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() + } + }