diff --git a/build.gradle.kts b/build.gradle.kts index b1c32f7..37cfe52 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,7 @@ repositories { } dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") implementation("net.java.dev.jna:jna-platform:5.13.0") implementation("org.greenrobot:eventbus-java:3.3.1") implementation("com.melloware:jintellitype:1.4.1") diff --git a/src/main/kotlin/gh/marad/tiler/app/internal/App.kt b/src/main/kotlin/gh/marad/tiler/app/internal/App.kt index 8d72618..9c2c548 100644 --- a/src/main/kotlin/gh/marad/tiler/app/internal/App.kt +++ b/src/main/kotlin/gh/marad/tiler/app/internal/App.kt @@ -4,10 +4,15 @@ import gh.marad.tiler.actions.ActionsFacade import gh.marad.tiler.actions.ReloadConfig import gh.marad.tiler.app.AppFacade import gh.marad.tiler.common.BroadcastingEventHandler +import gh.marad.tiler.common.TilerCommand import gh.marad.tiler.config.ConfigFacade import gh.marad.tiler.config.Hotkey import gh.marad.tiler.os.OsFacade import gh.marad.tiler.tiler.TilerFacade +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.slf4j.LoggerFactory import java.awt.MenuItem import java.awt.SystemTray @@ -24,11 +29,22 @@ class App(val config: ConfigFacade, val os: OsFacade, val tiler: TilerFacade, va actions.registerActionListener(ActionHandler(this, os, tiler)) val executor = TilerCommandsExecutorAndWatcher(os, config.getFilteringRules()) executor.execute(tiler.initializeWithOpenWindows()) + val commandChannel = Channel>(100) val evenHandler = BroadcastingEventHandler( - TilerWindowEventHandler(tiler, config.getFilteringRules(), os, executor), + TilerWindowEventHandler(tiler, config.getFilteringRules(), os, commandChannel), RestoreWindowsOnExitEventHandler(os) ) - os.startEventHandling(evenHandler) + + runBlocking { + launch { + for (commands in commandChannel) { + executor.execute(commands) + } + } + os.startEventHandling(evenHandler) + commandChannel.close() + coroutineContext.cancelChildren() + } } override fun reloadConfig() { diff --git a/src/main/kotlin/gh/marad/tiler/app/internal/TilerWindowEventHandler.kt b/src/main/kotlin/gh/marad/tiler/app/internal/TilerWindowEventHandler.kt index ffdb793..cae00e7 100644 --- a/src/main/kotlin/gh/marad/tiler/app/internal/TilerWindowEventHandler.kt +++ b/src/main/kotlin/gh/marad/tiler/app/internal/TilerWindowEventHandler.kt @@ -1,50 +1,54 @@ package gh.marad.tiler.app.internal +import gh.marad.tiler.common.TilerCommand import gh.marad.tiler.common.filteringrules.FilteringRules import gh.marad.tiler.os.OsFacade import gh.marad.tiler.common.Window import gh.marad.tiler.os.WindowEventHandler import gh.marad.tiler.tiler.TilerFacade +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.runBlocking class TilerWindowEventHandler( private val tiler: TilerFacade, private val filteringRules: FilteringRules, private val os: OsFacade, - private val executor: TilerCommandsExecutorAndWatcher + private val commandChannel: Channel> ): WindowEventHandler { - override fun windowActivated(window: Window) { + override fun windowActivated(window: Window) = runBlocking { if (filteringRules.shouldManage(window)) { - executor.execute(tiler.addWindow(window)) + commandChannel.send(tiler.addWindow(window)) } } - override fun windowAppeared(window: Window) { + override fun windowAppeared(window: Window) = runBlocking { if (filteringRules.shouldManage(window)) { - executor.execute(tiler.addWindow(window)) + commandChannel.send(tiler.addWindow(window)) } } - override fun windowDisappeared(window: Window) { - executor.execute(tiler.removeWindow(window)) + override fun windowDisappeared(window: Window) = runBlocking { + commandChannel.send(tiler.removeWindow(window)) } - override fun windowMinimized(window: Window) { - executor.execute(tiler.removeWindow(window)) + override fun windowMinimized(window: Window) = runBlocking { + commandChannel.send(tiler.removeWindow(window)) } - override fun windowRestored(window: Window) { + override fun windowRestored(window: Window) = runBlocking { if (filteringRules.shouldManage(window)) { - executor.execute(tiler.addWindow(window)) + commandChannel.send(tiler.addWindow(window)) } } - override fun windowMovedOrResized(window: Window) { - if (!filteringRules.shouldManage(window)) return - val foundWindow = os.windowsUnderCursor().lastOrNull { it.isVisible } - if (foundWindow != null && foundWindow.id != window.id) { - executor.execute(tiler.swapWindows(window, foundWindow)) - } else { - executor.execute(tiler.retile()) + override fun windowMovedOrResized(window: Window) = runBlocking { + if (filteringRules.shouldManage(window)) { + val foundWindow = os.windowsUnderCursor().lastOrNull { it.isVisible } + if (foundWindow != null && foundWindow.id != window.id) { + commandChannel.send(tiler.swapWindows(window, foundWindow)) + } else { + commandChannel.send(tiler.retile()) + } } } diff --git a/src/main/kotlin/gh/marad/tiler/common/TilerCommands.kt b/src/main/kotlin/gh/marad/tiler/common/TilerCommands.kt index e3f42c8..5edb275 100644 --- a/src/main/kotlin/gh/marad/tiler/common/TilerCommands.kt +++ b/src/main/kotlin/gh/marad/tiler/common/TilerCommands.kt @@ -10,6 +10,6 @@ data class SetWindowPosition(val windowId: WindowId, val position: WindowPositio /** * Minimizes given window */ -data class MinimizeWindow(val windowId: WindowId) : TilerCommand +data class HideWindow(val windowId: WindowId) : TilerCommand data class ShowWindow(val windowId: WindowId) : TilerCommand data class ActivateWindow(val windowId: WindowId) : TilerCommand \ No newline at end of file diff --git a/src/main/kotlin/gh/marad/tiler/os/internal/WindowsOs.kt b/src/main/kotlin/gh/marad/tiler/os/internal/WindowsOs.kt index 90a94eb..6101dc2 100644 --- a/src/main/kotlin/gh/marad/tiler/os/internal/WindowsOs.kt +++ b/src/main/kotlin/gh/marad/tiler/os/internal/WindowsOs.kt @@ -71,7 +71,7 @@ class WindowsOs : OsFacade { User32.INSTANCE.SetWindowPlacement(hwnd, placement) } - is MinimizeWindow -> { + is HideWindow -> { val hwnd = (command.windowId as WID).handle User32.INSTANCE.ShowWindow(hwnd, User32.SW_HIDE) } diff --git a/src/main/kotlin/gh/marad/tiler/tiler/internal/Tiler.kt b/src/main/kotlin/gh/marad/tiler/tiler/internal/Tiler.kt index b25a9a1..66ef816 100644 --- a/src/main/kotlin/gh/marad/tiler/tiler/internal/Tiler.kt +++ b/src/main/kotlin/gh/marad/tiler/tiler/internal/Tiler.kt @@ -60,7 +60,7 @@ class Tiler( override fun removeWindow(window: Window): List { viewManager.currentView().removeWindow(window.id) val windowToActivate = viewManager.currentView().windowToActivate() - return if (windowToActivate != null) { + return if (!viewManager.currentView().hasWindow(os.activeWindow().id) && windowToActivate != null) { retile() + ActivateWindow(windowToActivate) } else { retile() @@ -68,13 +68,13 @@ class Tiler( } override fun moveWindow(window: TilerWindow, viewId: Int): List { - if (!enabled) return emptyList() + if (!enabled || viewId == viewManager.currentViewId) return emptyList() viewManager.moveWindow(window.id, viewId) val windowToActivate = viewManager.currentView().windowToActivate() - return if (windowToActivate != null) { - (listOf(MinimizeWindow(window.id), ActivateWindow(windowToActivate)) + retile()) + return if (!viewManager.currentView().hasWindow(os.activeWindow().id) && windowToActivate != null) { + (listOf(HideWindow(window.id), ActivateWindow(windowToActivate)) + retile()) } else { - (listOf(MinimizeWindow(window.id)) + retile()) + (listOf(HideWindow(window.id)) + retile()) } } diff --git a/src/main/kotlin/gh/marad/tiler/tiler/internal/views/ViewManager.kt b/src/main/kotlin/gh/marad/tiler/tiler/internal/views/ViewManager.kt index 729d3c4..379b563 100644 --- a/src/main/kotlin/gh/marad/tiler/tiler/internal/views/ViewManager.kt +++ b/src/main/kotlin/gh/marad/tiler/tiler/internal/views/ViewManager.kt @@ -7,6 +7,9 @@ class ViewManager(private val defaultLayout: () -> Layout) { private var _activeViewId: Int = 0 private val _views = mutableMapOf(0 to View(layout = defaultLayout())) + val currentViewId: Int + get() = _activeViewId + @Suppress("MemberVisibilityCanBePrivate") fun getView(viewId: Int): View = _views.getOrPut(viewId) { View(layout = defaultLayout()) } diff --git a/src/main/kotlin/gh/marad/tiler/tiler/internal/views/ViewSwitcher.kt b/src/main/kotlin/gh/marad/tiler/tiler/internal/views/ViewSwitcher.kt index 80391a5..5b082fd 100644 --- a/src/main/kotlin/gh/marad/tiler/tiler/internal/views/ViewSwitcher.kt +++ b/src/main/kotlin/gh/marad/tiler/tiler/internal/views/ViewSwitcher.kt @@ -29,7 +29,7 @@ class ViewSwitcher(private val viewManager: ViewManager, private val filteringRu .map { ShowWindow(it.id) } val minimizeCommands = view.filterWindowsNotInView(desktopState.getManagableWindows(filteringRules)) .filterNot { it.isMinimized || !it.isVisible } - .map { MinimizeWindow(it.id) } + .map { HideWindow(it.id) } val windowToActivate = view.windowToActivate() return (minimizeCommands + showCommands) diff --git a/src/test/kotlin/gr/marad/tiler/GraphicalTest.kt b/src/test/kotlin/gr/marad/tiler/GraphicalTest.kt index 7c5925f..87fe13c 100644 --- a/src/test/kotlin/gr/marad/tiler/GraphicalTest.kt +++ b/src/test/kotlin/gr/marad/tiler/GraphicalTest.kt @@ -15,6 +15,7 @@ import io.kotest.property.Arb import io.kotest.property.arbitrary.arbitrary import io.kotest.property.arbitrary.int import io.kotest.property.arbitrary.next +import kotlinx.coroutines.channels.Channel import org.jetbrains.skija.* import org.lwjgl.glfw.GLFW.* import org.lwjgl.opengl.GL @@ -84,7 +85,7 @@ val os = object : OsFacade { } } } - is MinimizeWindow -> { + is HideWindow -> { desktopWindows.replaceAll { if (it.window.id == cmd.windowId) { it.copy(minimized = true) @@ -105,8 +106,7 @@ val os = object : OsFacade { } val config = ConfigFacade.createConfig(os) val tiler = TilerFacade.createTiler(config, os) -val executor = TilerCommandsExecutorAndWatcher(os, filteringRules) -val eventHandler = TilerWindowEventHandler(tiler, filteringRules, os, executor) +val eventHandler = TilerWindowEventHandler(tiler, filteringRules, os, Channel()) val xGen2 = Arb.int(0, width -10) val yGen2 = Arb.int(0, height -10)