Skip to content

Commit

Permalink
🐛 Fix issues related to window interaction (#1255)
Browse files Browse the repository at this point in the history
  • Loading branch information
guiyanakuang authored Jun 15, 2024
1 parent 19d1d6d commit fa5973f
Show file tree
Hide file tree
Showing 17 changed files with 166 additions and 72 deletions.
28 changes: 22 additions & 6 deletions composeApp/src/commonMain/kotlin/com/clipevery/ClipeveryApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusTarget
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp
import com.clipevery.app.AppWindowManager
import com.clipevery.ui.AboutView
import com.clipevery.ui.ClipeveryTheme
import com.clipevery.ui.HomeView
Expand All @@ -31,13 +34,18 @@ import com.clipevery.ui.devices.DeviceDetailView
import com.clipevery.ui.devices.TokenView
import com.clipevery.ui.settings.SettingsView
import com.clipevery.ui.settings.ShortcutKeysView
import com.clipevery.utils.GlobalCoroutineScope
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.launch

@Composable
fun ClipeveryWindow(hideWindow: () -> Unit) {
fun ClipeveryWindow(hideWindow: suspend () -> Unit) {
val current = LocalKoinApplication.current
val appWindowManager = current.koin.get<AppWindowManager>()
val toastManager = current.koin.get<ToastManager>()
val dialogService = current.koin.get<DialogService>()

val globalCoroutineScope = current.koin.get<GlobalCoroutineScope>()
val mainCoroutineDispatcher = globalCoroutineScope.mainCoroutineDispatcher
val toast by toastManager.toast

ClipeveryTheme {
Expand All @@ -48,13 +56,19 @@ fun ClipeveryWindow(hideWindow: () -> Unit) {
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = {
hideWindow()
mainCoroutineDispatcher.launch(CoroutineName("Hide Clipevery")) {
hideWindow()
}
},
onTap = {
hideWindow()
mainCoroutineDispatcher.launch(CoroutineName("Hide Clipevery")) {
hideWindow()
}
},
onLongPress = {
hideWindow()
mainCoroutineDispatcher.launch(CoroutineName("Hide Clipevery")) {
hideWindow()
}
},
onPress = {},
)
Expand Down Expand Up @@ -84,7 +98,9 @@ fun ClipeveryWindow(hideWindow: () -> Unit) {
Modifier
.clip(RoundedCornerShape(10.dp))
.background(MaterialTheme.colors.background)
.fillMaxWidth(),
.fillMaxWidth()
.focusTarget()
.focusRequester(appWindowManager.mainFocusRequester),
horizontalAlignment = Alignment.CenterHorizontally,
) {
ClipeveryContent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ interface AppWindowManager {

var mainWindowState: WindowState

var mainFocusRequester: FocusRequester

var showMainDialog: Boolean

var showSearchWindow: Boolean
Expand All @@ -22,11 +24,11 @@ interface AppWindowManager {

fun getCurrentActiveAppName(): String?

fun activeMainWindow()
suspend fun activeMainWindow()

fun unActiveMainWindow()
suspend fun unActiveMainWindow()

fun switchMainWindow() {
suspend fun switchMainWindow() {
if (showMainWindow) {
unActiveMainWindow()
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.clipevery.utils

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope

expect val ioDispatcher: CoroutineDispatcher

Expand All @@ -9,3 +10,12 @@ expect val mainDispatcher: CoroutineDispatcher
expect val cpuDispatcher: CoroutineDispatcher

expect val unconfinedDispatcher: CoroutineDispatcher

interface GlobalCoroutineScope {

val mainCoroutineDispatcher: CoroutineScope

val ioCoroutineDispatcher: CoroutineScope

val cpuCoroutineDispatcher: CoroutineScope
}
10 changes: 8 additions & 2 deletions composeApp/src/desktopMain/kotlin/com/clipevery/Clipevery.kt
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ import com.clipevery.ui.base.UISupport
import com.clipevery.ui.resource.ClipResourceLoader
import com.clipevery.ui.resource.DesktopAbsoluteClipResourceLoader
import com.clipevery.ui.search.ClipeverySearchWindow
import com.clipevery.utils.GlobalCoroutineScope
import com.clipevery.utils.GlobalCoroutineScopeImpl
import com.clipevery.utils.GlobalCoroutineScopeImpl.mainCoroutineDispatcher
import com.clipevery.utils.IDGenerator
import com.clipevery.utils.IDGeneratorFactory
import com.clipevery.utils.QRCodeGenerator
Expand Down Expand Up @@ -197,6 +200,7 @@ class Clipevery {
single<AppStartUpService> { DesktopAppStartUpService(get()) }
single<AppRestartService> { DesktopAppRestartService }
single<EndpointInfoFactory> { DesktopEndpointInfoFactory(lazy { get<ClipServer>() }) }
single<GlobalCoroutineScope> { GlobalCoroutineScopeImpl }
single<SyncInfoFactory> { DesktopSyncInfoFactory(get(), get()) }
single<PathProvider> { DesktopPathProvider }
single<FilePersist> { DesktopFilePersist }
Expand Down Expand Up @@ -434,8 +438,10 @@ class Clipevery {
}

override fun windowLostFocus(e: WindowEvent?) {
if (!appWindowManager.showMainDialog) {
appWindowManager.unActiveMainWindow()
mainCoroutineDispatcher.launch(CoroutineName("Hide Clipevery")) {
if (!appWindowManager.showMainDialog) {
appWindowManager.unActiveMainWindow()
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ abstract class AbstractAppWindowManager : AppWindowManager {
),
)

override var mainFocusRequester = FocusRequester()

override var showMainDialog by mutableStateOf(false)

override var showSearchWindow by mutableStateOf(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,19 @@ class LinuxAppWindowManager(
}
}

override fun activeMainWindow() {
override suspend fun activeMainWindow() {
logger.info { "active main window" }
showMainWindow = true
prevLinuxAppInfo = X11Api.bringToFront(MAIN_WINDOW_TITLE)
delay(500)
mainFocusRequester.requestFocus()
}

override fun unActiveMainWindow() {
override suspend fun unActiveMainWindow() {
logger.info { "unActive main window" }
X11Api.bringToBack(prevLinuxAppInfo)
showMainWindow = false
mainFocusRequester.freeFocus()
}

override suspend fun activeSearchWindow() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class MacAppWindowManager(
}
}

override fun activeMainWindow() {
override suspend fun activeMainWindow() {
logger.info { "active main window" }
showMainWindow = true
MacosApi.INSTANCE.bringToFront(MAIN_WINDOW_TITLE).let {
Expand All @@ -73,19 +73,17 @@ class MacAppWindowManager(
}
}
}
delay(500)
mainFocusRequester.requestFocus()
}

override fun unActiveMainWindow() {
override suspend fun unActiveMainWindow() {
logger.info { "unActive main window" }
val pair = macPasteUtils.getPasteMemory()
MacosApi.INSTANCE.bringToBack(
MAIN_WINDOW_TITLE,
MacosApi.INSTANCE.mainToBack(
prevMacAppInfo?.bundleIdentifier ?: "",
toPaste = false,
pair.first,
pair.second,
)
showMainWindow = false
mainFocusRequester.freeFocus()
}

override suspend fun activeSearchWindow() {
Expand Down Expand Up @@ -113,8 +111,7 @@ class MacAppWindowManager(
logger.info { "unActive search window" }
val toPaste = preparePaste()
val pair = macPasteUtils.getPasteMemory()
MacosApi.INSTANCE.bringToBack(
SEARCH_WINDOW_TITLE,
MacosApi.INSTANCE.searchToBack(
prevMacAppInfo?.bundleIdentifier ?: "",
toPaste = toPaste,
pair.first,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,23 @@ class WinAppWindowManager(
}
}

override fun activeMainWindow() {
override suspend fun activeMainWindow() {
logger.info { "active main window" }
showMainWindow = true
prevWinAppInfo = User32.bringToFront(MAIN_WINDOW_TITLE, mainHWND, searchHWND)
delay(500)
mainFocusRequester.requestFocus()
}

override fun unActiveMainWindow() {
override suspend fun unActiveMainWindow() {
logger.info { "unActive main window" }
val keyCodes =
lazyShortcutKeys.value.shortcutKeysCore.keys["Paste"]?.let {
it.map { key -> key.rawCode }
} ?: listOf()
User32.bringToBack(MAIN_WINDOW_TITLE, mainHWND, searchHWND, prevWinAppInfo?.hwnd, false, keyCodes)
showMainWindow = false
mainFocusRequester.freeFocus()
}

override suspend fun activeSearchWindow() {
Expand All @@ -90,9 +93,10 @@ class WinAppWindowManager(
searchWindowState.position = calPosition(graphicsDevice.defaultConfiguration.bounds)
}

prevWinAppInfo = User32.bringToFront(SEARCH_WINDOW_TITLE, mainHWND, searchHWND)

// Wait for the window to be ready, otherwise bringToFront may cause the window to fail to get focus
delay(500)

prevWinAppInfo = User32.bringToFront(SEARCH_WINDOW_TITLE, mainHWND, searchHWND)
searchFocusRequester.requestFocus()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import com.clipevery.dao.clip.ClipDao
import com.clipevery.dao.clip.ClipData
import com.clipevery.listener.ShortcutKeysAction
import com.clipevery.ui.base.DialogService
import com.clipevery.utils.mainDispatcher
import com.clipevery.utils.GlobalCoroutineScopeImpl.mainCoroutineDispatcher
import io.github.oshai.kotlinlogging.KotlinLogging
import io.realm.kotlin.query.RealmQuery
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

class DesktopShortKeysAction(
Expand All @@ -28,8 +27,6 @@ class DesktopShortKeysAction(

private val logger = KotlinLogging.logger {}

private val mainDispatcherScope = CoroutineScope(mainDispatcher)

override val action: (String) -> Unit = { actionName ->
when (actionName) {
"Paste_Local_Last" -> pasteLast(true)
Expand All @@ -44,21 +41,21 @@ class DesktopShortKeysAction(

private fun showMainWindow() {
logger.info { "Open main window" }
mainDispatcherScope.launch(CoroutineName("OpenMainWindow")) {
mainCoroutineDispatcher.launch(CoroutineName("OpenMainWindow")) {
appWindowManager.activeMainWindow()
}
}

private fun showSearchWindow() {
logger.info { "Open search window" }
mainDispatcherScope.launch(CoroutineName("OpenSearchWindow")) {
mainCoroutineDispatcher.launch(CoroutineName("OpenSearchWindow")) {
clipSearchService.activeWindow()
}
}

private fun hideWindow() {
logger.info { "Hide window" }
mainDispatcherScope.launch(CoroutineName("HideWindow")) {
mainCoroutineDispatcher.launch(CoroutineName("HideWindow")) {
if (appWindowManager.showMainWindow && dialogService.dialogs.isEmpty()) {
appWindowManager.unActiveMainWindow()
}
Expand All @@ -78,7 +75,7 @@ class DesktopShortKeysAction(
} else {
{ it.query("appInstanceId != $0", appInfo.appInstanceId) }
}
mainDispatcherScope.launch(CoroutineName("Paste")) {
mainCoroutineDispatcher.launch(CoroutineName("Paste")) {
val result =
clipDao.searchClipData(
searchTerms = listOf(),
Expand All @@ -96,14 +93,14 @@ class DesktopShortKeysAction(

private fun switchMonitorPasteboard() {
logger.info { "Switch Monitor Pasteboard" }
mainDispatcherScope.launch(CoroutineName("SwitchMonitorPasteboard")) {
mainCoroutineDispatcher.launch(CoroutineName("SwitchMonitorPasteboard")) {
clipboardService.toggle()
}
}

private fun switchEncrypt() {
logger.info { "Switch Encrypt" }
mainDispatcherScope.launch(CoroutineName("SwitchEncrypt")) {
mainCoroutineDispatcher.launch(CoroutineName("SwitchEncrypt")) {
configManager.updateConfig { config -> config.copy(isEncryptSync = !configManager.config.isEncryptSync) }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ interface MacosApi : Library {
path: String,
)

fun bringToBack(
windowTitle: String,
fun mainToBack(appName: String)

fun searchToBack(
appName: String,
toPaste: Boolean,
array: Pointer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package com.clipevery.ui
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowPosition
import com.clipevery.app.AppWindowManager
import com.clipevery.utils.GlobalCoroutineScopeImpl.mainCoroutineDispatcher
import com.clipevery.utils.getResourceUtils
import dorkbox.systemTray.MenuItem
import dorkbox.systemTray.SystemTray
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.launch
import org.koin.core.KoinApplication
import java.awt.GraphicsEnvironment
import java.awt.Toolkit
Expand All @@ -23,7 +26,11 @@ object LinuxTrayView {
systemTray.setImage(resourceUtils.resourceInputStream("icon/clipevery.tray.linux.png"))
systemTray.setTooltip("Clipevery")
systemTray.menu?.add(
MenuItem("Open Clipevery") { appWindowManager.activeMainWindow() },
MenuItem("Open Clipevery") {
mainCoroutineDispatcher.launch(CoroutineName("Open Clipevery")) {
appWindowManager.activeMainWindow()
}
},
)

systemTray.menu?.add(
Expand Down
Loading

0 comments on commit fa5973f

Please sign in to comment.