From c0904c68c50db2ddabf5eb69473623c34041341e Mon Sep 17 00:00:00 2001 From: Yiqun Zhang Date: Sun, 25 Feb 2024 14:42:18 +0800 Subject: [PATCH] :hammer: Use coroutines instead of java thread-style pasteboard listening (#381) --- .../com/clipevery/clip/ClipboardMonitor.kt | 2 + .../com/clipevery/clip/ClipboardService.kt | 2 +- .../os/macos/MacosClipboardService.kt | 71 +++++++++---------- .../os/windows/WindowsClipboardService.kt | 37 ++++------ 4 files changed, 48 insertions(+), 64 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipboardMonitor.kt b/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipboardMonitor.kt index e1bf4e553..d1f39050a 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipboardMonitor.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipboardMonitor.kt @@ -4,4 +4,6 @@ interface ClipboardMonitor { fun start() fun stop() + + fun run() } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipboardService.kt b/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipboardService.kt index d5b98e1ea..97d173d01 100644 --- a/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipboardService.kt +++ b/composeApp/src/commonMain/kotlin/com/clipevery/clip/ClipboardService.kt @@ -1,6 +1,6 @@ package com.clipevery.clip -interface ClipboardService: Runnable, ClipboardMonitor { +interface ClipboardService: ClipboardMonitor { val clipConsumer: TransferableConsumer diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/os/macos/MacosClipboardService.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/os/macos/MacosClipboardService.kt index a58c11edf..951df630c 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/os/macos/MacosClipboardService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/os/macos/MacosClipboardService.kt @@ -3,62 +3,57 @@ package com.clipevery.os.macos import com.clipevery.clip.ClipboardService import com.clipevery.clip.TransferableConsumer import com.clipevery.os.macos.api.MacosApi +import com.clipevery.utils.cpuDispatcher +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking 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 -class MacosClipboardService - (override val clipConsumer: TransferableConsumer) : ClipboardService { +class MacosClipboardService(override val clipConsumer: TransferableConsumer): ClipboardService { - private var executor: ScheduledExecutorService? = null + private val logger = KotlinLogging.logger {} private var changeCount = 0 private var systemClipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard - override fun run() { - try { - MacosApi.INSTANCE.getClipboardChangeCount().let { currentChangeCount -> - if (changeCount == currentChangeCount) { - return - } - changeCount = currentChangeCount - val contents: Transferable? = systemClipboard.getContents(null) - contents?.let { - clipConsumer.consume(it) + private var job: Job? = null + + override fun run(): Unit = runBlocking { + launch { + while (isActive) { + try { + MacosApi.INSTANCE.getClipboardChangeCount().let { currentChangeCount -> + if (changeCount != currentChangeCount) { + changeCount = currentChangeCount + val contents = systemClipboard.getContents(null) + contents?.let { + clipConsumer.consume(it) + } + } + } + } catch (e: Exception) { + logger.error(e) { "Failed to consume transferable" } } + delay(300L) } - } catch (e: java.lang.Exception) { - e.printStackTrace() } } override fun start() { - if (executor?.isShutdown != false) { - executor = Executors.newScheduledThreadPool(2) { r -> Thread(r, "Clipboard Monitor") } + if (job?.isActive != true) { + job = CoroutineScope(cpuDispatcher).launch { + run() + } } - 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() - } - } + job?.cancel() } } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/clipevery/os/windows/WindowsClipboardService.kt b/composeApp/src/desktopMain/kotlin/com/clipevery/os/windows/WindowsClipboardService.kt index a372484f0..c620b3b66 100644 --- a/composeApp/src/desktopMain/kotlin/com/clipevery/os/windows/WindowsClipboardService.kt +++ b/composeApp/src/desktopMain/kotlin/com/clipevery/os/windows/WindowsClipboardService.kt @@ -2,20 +2,21 @@ package com.clipevery.os.windows import com.clipevery.clip.ClipboardService import com.clipevery.clip.TransferableConsumer -import com.clipevery.platform.currentPlatform import com.clipevery.os.windows.api.User32 +import com.clipevery.platform.currentPlatform +import com.clipevery.utils.ioDispatcher import com.sun.jna.Pointer import com.sun.jna.platform.win32.Kernel32 import com.sun.jna.platform.win32.WinDef.HWND import com.sun.jna.platform.win32.WinDef.LPARAM import com.sun.jna.platform.win32.WinDef.WPARAM import com.sun.jna.platform.win32.WinUser.MSG +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import java.awt.Toolkit import java.awt.datatransfer.Clipboard import java.awt.datatransfer.Transferable -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit class WindowsClipboardService @@ -23,7 +24,7 @@ class WindowsClipboardService private var systemClipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard - private var executor: ExecutorService? = null + private var job: Job? = null private var viewer: HWND? = null private var nextViewer: HWND? = null private val event = Kernel32.INSTANCE.CreateEvent( @@ -69,30 +70,16 @@ class WindowsClipboardService } override fun start() { - if (executor?.isShutdown != false) { - executor = Executors.newSingleThreadExecutor { r -> Thread(r, "Clipboard Monitor") } + if (job?.isActive != true) { + job = CoroutineScope(ioDispatcher).launch { + run() + } } - executor?.execute(this) } override fun stop() { - executor?.let { - Kernel32.INSTANCE.SetEvent(event) - 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() - } - } + Kernel32.INSTANCE.SetEvent(event) + job?.cancel() } private fun onChange() {