Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔨 Use coroutines instead of java thread-style pasteboard listening #381

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ interface ClipboardMonitor {
fun start()

fun stop()

fun run()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.clipevery.clip

interface ClipboardService: Runnable, ClipboardMonitor {
interface ClipboardService: ClipboardMonitor {

val clipConsumer: TransferableConsumer

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,29 @@ 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
(override val clipConsumer: TransferableConsumer) : ClipboardService, User32.WNDPROC {

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(
Expand Down Expand Up @@ -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() {
Expand Down
Loading