diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt
index cc683da8f..1499edf8f 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/LoginFailedException.kt
@@ -36,6 +36,12 @@ class WrongPasswordException(message: String?) : LoginFailedException(true, mess
  */
 class NoServerAvailableException(override val cause: Throwable?) : LoginFailedException(false, "no server available")
 
+/**
+ * 无标准输入或 Kotlin 不支持此输入.
+ */
+class NoStandardInputForCaptchaException(override val cause: Throwable?) :
+    LoginFailedException(true, "no standard input for captcha")
+
 /**
  * 需要短信验证时抛出. mirai 目前还不支持短信验证.
  */
diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.jvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.jvm.kt
index 58aaeced7..0f8027cf9 100644
--- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.jvm.kt
+++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.jvm.kt
@@ -22,7 +22,7 @@ import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
 import kotlinx.io.core.use
 import net.mamoe.mirai.Bot
-import java.awt.Desktop
+import net.mamoe.mirai.network.NoStandardInputForCaptchaException
 import java.awt.Image
 import java.awt.image.BufferedImage
 import java.io.File
@@ -32,6 +32,9 @@ import kotlin.coroutines.CoroutineContext
 
 actual typealias Throws = kotlin.jvm.Throws
 
+/**
+ * 自动选择 [SwingSolver] 或 [StandardCharImageLoginSolver]
+ */
 @MiraiExperimentalAPI
 class DefaultLoginSolver(
     val input: suspend () -> String,
@@ -40,10 +43,10 @@ class DefaultLoginSolver(
     private val delegate: LoginSolver
 
     init {
-        if (WindowHelperJvm.isDesktopSupport) {
+        if (WindowHelperJvm.isDesktopSupported) {
             delegate = SwingSolver
         } else {
-            delegate = DefaultLoginSolverImpl(input, overrideLogger)
+            delegate = StandardCharImageLoginSolver(input, overrideLogger)
         }
     }
 
@@ -60,9 +63,15 @@ class DefaultLoginSolver(
     }
 }
 
+/**
+ * 使用字符图片展示验证码, 使用 [input] 获取输入, 使用 [overrideLogger] 输出
+ */
 @MiraiExperimentalAPI
-class DefaultLoginSolverImpl(
+class StandardCharImageLoginSolver(
     input: suspend () -> String,
+    /**
+     * 为 `null` 时使用 [Bot.logger]
+     */
     private val overrideLogger: MiraiLogger? = null
 ) : LoginSolver() {
     private val input: suspend () -> String = suspend {
@@ -135,16 +144,9 @@ actual abstract class LoginSolver {
     actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String?
 
     actual companion object {
-        actual val Default: LoginSolver
+        actual val Default: LoginSolver =
             @OptIn(MiraiExperimentalAPI::class)
-            get() {
-                if (Desktop.isDesktopSupported()) {
-                    return SwingSolver
-                }
-                return DefaultLoginSolverImpl(input = {
-                    withContext(Dispatchers.IO) { readLine() } ?: error("No standard input")
-                })
-            }
+            DefaultLoginSolver({ readLine() ?: throw NoStandardInputForCaptchaException(null) })
     }
 }
 
diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.swing.jvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.swing.jvm.kt
index 0855c4cc6..3857b61c2 100644
--- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.swing.jvm.kt
+++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.swing.jvm.kt
@@ -20,6 +20,7 @@ import javax.swing.JTextField
 /**
  * @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
  */
+@SinceMirai("0.39.2")
 @MiraiExperimentalAPI
 object SwingSolver : LoginSolver() {
     override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? {
diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WindowHelperJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WindowHelperJvm.kt
index d45592bd4..c9487dc6f 100644
--- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WindowHelperJvm.kt
+++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WindowHelperJvm.kt
@@ -25,7 +25,7 @@ import javax.swing.JTextField
 
 // 隔离类代码
 internal object WindowHelperJvm {
-    internal val isDesktopSupport: Boolean =
+    internal val isDesktopSupported: Boolean =
         kotlin.runCatching {
             Desktop.isDesktopSupported()
         }.getOrElse {