From ceeb547fb8d9a32e992aa1e72d107cf0656c2ed6 Mon Sep 17 00:00:00 2001 From: Timo T Date: Fri, 6 May 2022 07:42:30 +0200 Subject: [PATCH] Create turn notifier for when the game is running for Windows (#6682) * Create turn notifier for when the game is running for Windows If playing on Desktop, you often put the game into background, but still want to know if it's your turn. A standard Windows function for that is `FlashWindow` from winuser.h, which is implemented here * Fix: Use the window from the listener instead of the static one from libGDL * Only notify if it's the turn of the player that is playing * Always notify spectators of the next players' turn * Refactor: Move notifier into GeneralPlatformSpecificHelpers * Only load Windows DLL when we're actually on Windows --- build.gradle.kts | 3 + .../utils/GeneralPlatformSpecificHelpers.kt | 7 +- .../com/unciv/ui/worldscreen/WorldScreen.kt | 3 + .../com/unciv/app/desktop/DesktopLauncher.kt | 3 +- .../desktop/MultiplayerTurnNotifierDesktop.kt | 64 +++++++++++++++++++ .../desktop/PlatformSpecificHelpersDesktop.kt | 13 +++- 6 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 desktop/src/com/unciv/app/desktop/MultiplayerTurnNotifierDesktop.kt diff --git a/build.gradle.kts b/build.gradle.kts index af25100881..5c6e138197 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -71,6 +71,9 @@ project(":desktop") { "implementation"("com.github.MinnDevelopment:java-discord-rpc:v2.0.1") + "implementation"("net.java.dev.jna:jna:5.11.0") + "implementation"("net.java.dev.jna:jna-platform:5.11.0") + // For server-side "implementation"("io.ktor:ktor-server-core:1.6.8") diff --git a/core/src/com/unciv/ui/utils/GeneralPlatformSpecificHelpers.kt b/core/src/com/unciv/ui/utils/GeneralPlatformSpecificHelpers.kt index e12cbb719a..0680ab4bb4 100644 --- a/core/src/com/unciv/ui/utils/GeneralPlatformSpecificHelpers.kt +++ b/core/src/com/unciv/ui/utils/GeneralPlatformSpecificHelpers.kt @@ -11,7 +11,12 @@ interface GeneralPlatformSpecificHelpers { * @param allow `true`: allow all orientations (follows sensor as limited by OS settings) * `false`: allow only landscape orientations (both if supported, otherwise default landscape only) */ - fun allowPortrait(allow: Boolean) + fun allowPortrait(allow: Boolean) {} fun isInternetConnected(): Boolean + + /** + * Notifies the user that it's their turn while the game is running + */ + fun notifyTurnStarted() {} } diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 49071436fe..e5fcecabd2 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -363,6 +363,9 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas // stuff has changed and the "waiting for X" will now show the correct civ stopMultiPlayerRefresher() latestGame.isUpToDate = true + if (viewingCiv.civName == latestGame.currentPlayer || viewingCiv.civName == Constants.spectator) { + game.platformSpecificHelper?.notifyTurnStarted() + } postCrashHandlingRunnable { createNewWorldScreen(latestGame) } } diff --git a/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt b/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt index 6b5dfd0a2e..bfa0158b4b 100644 --- a/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt +++ b/desktop/src/com/unciv/app/desktop/DesktopLauncher.kt @@ -46,13 +46,14 @@ internal object DesktopLauncher { UniqueDocsWriter().write() } + val platformSpecificHelper = PlatformSpecificHelpersDesktop(config) val desktopParameters = UncivGameParameters( versionFromJar, cancelDiscordEvent = { discordTimer?.cancel() }, fontImplementation = NativeFontDesktop(Fonts.ORIGINAL_FONT_SIZE.toInt(), settings.fontFamily), customSaveLocationHelper = CustomSaveLocationHelperDesktop(), crashReportSysInfo = CrashReportSysInfoDesktop(), - platformSpecificHelper = PlatformSpecificHelpersDesktop(), + platformSpecificHelper = platformSpecificHelper, audioExceptionHelper = HardenGdxAudio() ) diff --git a/desktop/src/com/unciv/app/desktop/MultiplayerTurnNotifierDesktop.kt b/desktop/src/com/unciv/app/desktop/MultiplayerTurnNotifierDesktop.kt new file mode 100644 index 0000000000..bb8a5a00d2 --- /dev/null +++ b/desktop/src/com/unciv/app/desktop/MultiplayerTurnNotifierDesktop.kt @@ -0,0 +1,64 @@ +package com.unciv.app.desktop + +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Window +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3WindowAdapter +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.platform.win32.User32 +import com.sun.jna.platform.win32.WinNT +import com.sun.jna.platform.win32.WinUser +import org.lwjgl.glfw.GLFWNativeWin32 + +class MultiplayerTurnNotifierDesktop: Lwjgl3WindowAdapter() { + companion object { + val user32: User32? = try { + if (System.getProperty("os.name")?.contains("Windows") == true) { + Native.load(User32::class.java) + } else { + null + } + } catch (e: UnsatisfiedLinkError) { + println("Error while initializing turn notifier: " + e.message) + null + } + } + private var window: Lwjgl3Window? = null + private var hasFocus: Boolean = true + + override fun created(window: Lwjgl3Window?) { + this.window = window + } + + override fun focusLost() { + hasFocus = false + } + + override fun focusGained() { + hasFocus = true + } + + + fun turnStarted() { + flashWindow() + } + + /** + * See https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-flashwindowex + * + * We should've used FlashWindow instead of FlashWindowEx, but for some reason the former has no binding in Java's User32 + */ + private fun flashWindow() { + try { + if (user32 == null || window == null || hasFocus) return + val flashwinfo = WinUser.FLASHWINFO() + val hwnd = GLFWNativeWin32.glfwGetWin32Window(window!!.windowHandle) + flashwinfo.hWnd = WinNT.HANDLE(Pointer.createConstant(hwnd)) + flashwinfo.dwFlags = 3 // FLASHW_ALL + flashwinfo.uCount = 3 + user32.FlashWindowEx(flashwinfo) + } catch (e: Throwable) { + /** try to ignore even if we get an [Error], just log it */ + println("Error while notifying the user of their turn: " + e.message) + } + } +} diff --git a/desktop/src/com/unciv/app/desktop/PlatformSpecificHelpersDesktop.kt b/desktop/src/com/unciv/app/desktop/PlatformSpecificHelpersDesktop.kt index 90539ac95d..8e4da875cd 100644 --- a/desktop/src/com/unciv/app/desktop/PlatformSpecificHelpersDesktop.kt +++ b/desktop/src/com/unciv/app/desktop/PlatformSpecificHelpersDesktop.kt @@ -1,11 +1,13 @@ package com.unciv.app.desktop +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration import com.unciv.ui.utils.GeneralPlatformSpecificHelpers import java.net.InetAddress -class PlatformSpecificHelpersDesktop : GeneralPlatformSpecificHelpers { - override fun allowPortrait(allow: Boolean) { - // No need to do anything +class PlatformSpecificHelpersDesktop(config: Lwjgl3ApplicationConfiguration) : GeneralPlatformSpecificHelpers { + val turnNotifier = MultiplayerTurnNotifierDesktop() + init { + config.setWindowListener(turnNotifier); } override fun isInternetConnected(): Boolean { @@ -15,4 +17,9 @@ class PlatformSpecificHelpersDesktop : GeneralPlatformSpecificHelpers { false } } + + override fun notifyTurnStarted() { + turnNotifier.turnStarted() + } + }