|
|
@@ -0,0 +1,662 @@
|
|
|
+package net.mamoe.mirai.console
|
|
|
+
|
|
|
+import com.googlecode.lanterna.SGR
|
|
|
+import com.googlecode.lanterna.TerminalSize
|
|
|
+import com.googlecode.lanterna.TextColor
|
|
|
+import com.googlecode.lanterna.graphics.TextGraphics
|
|
|
+import com.googlecode.lanterna.input.KeyStroke
|
|
|
+import com.googlecode.lanterna.input.KeyType
|
|
|
+import com.googlecode.lanterna.terminal.DefaultTerminalFactory
|
|
|
+import com.googlecode.lanterna.terminal.Terminal
|
|
|
+import com.googlecode.lanterna.terminal.TerminalResizeListener
|
|
|
+import com.googlecode.lanterna.terminal.swing.SwingTerminal
|
|
|
+import com.googlecode.lanterna.terminal.swing.SwingTerminalFrame
|
|
|
+import kotlinx.coroutines.*
|
|
|
+import kotlinx.coroutines.io.close
|
|
|
+import kotlinx.io.core.IoBuffer
|
|
|
+import kotlinx.io.core.use
|
|
|
+import net.mamoe.mirai.Bot
|
|
|
+import net.mamoe.mirai.console.MiraiConsoleTerminalUI.LoggerDrawer.cleanPage
|
|
|
+import net.mamoe.mirai.console.MiraiConsoleTerminalUI.LoggerDrawer.drawLog
|
|
|
+import net.mamoe.mirai.console.MiraiConsoleTerminalUI.LoggerDrawer.redrawLogs
|
|
|
+import net.mamoe.mirai.console.utils.MiraiConsoleUI
|
|
|
+import net.mamoe.mirai.utils.LoginSolver
|
|
|
+import net.mamoe.mirai.utils.createCharImg
|
|
|
+import net.mamoe.mirai.utils.writeChannel
|
|
|
+import java.io.File
|
|
|
+import java.io.OutputStream
|
|
|
+import java.io.PrintStream
|
|
|
+import java.nio.charset.Charset
|
|
|
+import java.util.*
|
|
|
+import java.util.concurrent.ConcurrentHashMap
|
|
|
+import java.util.concurrent.ConcurrentLinkedDeque
|
|
|
+import javax.imageio.ImageIO
|
|
|
+import kotlin.concurrent.thread
|
|
|
+import kotlin.system.exitProcess
|
|
|
+
|
|
|
+/**
|
|
|
+ * 此文件不推荐任何人看
|
|
|
+ * 可能导致
|
|
|
+ * 1:心肌梗死
|
|
|
+ * 2:呼吸困难
|
|
|
+ * 3:想要重写但是发现改任何一个看似不合理的地方都会崩
|
|
|
+ *
|
|
|
+ * @author NaturalHG
|
|
|
+ *
|
|
|
+ */
|
|
|
+
|
|
|
+fun String.actualLength(): Int {
|
|
|
+ var x = 0
|
|
|
+ this.forEach {
|
|
|
+ if (it.isChineseChar()) {
|
|
|
+ x += 2
|
|
|
+ } else {
|
|
|
+ x += 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return x
|
|
|
+}
|
|
|
+
|
|
|
+fun String.getSubStringIndexByActualLength(widthMax: Int): Int {
|
|
|
+ var index = 0
|
|
|
+ var currentLength = 0
|
|
|
+ this.forEach {
|
|
|
+ if (it.isChineseChar()) {
|
|
|
+ currentLength += 2
|
|
|
+ } else {
|
|
|
+ currentLength += 1
|
|
|
+ }
|
|
|
+ if (currentLength > widthMax) {
|
|
|
+ return@forEach
|
|
|
+ }
|
|
|
+ ++index
|
|
|
+ }
|
|
|
+ if (index < 2) {
|
|
|
+ index = 2
|
|
|
+ }
|
|
|
+ return index
|
|
|
+}
|
|
|
+
|
|
|
+fun Char.isChineseChar(): Boolean {
|
|
|
+ return this.toString().isChineseChar()
|
|
|
+}
|
|
|
+
|
|
|
+fun String.isChineseChar(): Boolean {
|
|
|
+ return this.matches(Regex("[\u4e00-\u9fa5]"))
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+object MiraiConsoleTerminalUI : MiraiConsoleUI {
|
|
|
+ val cacheLogSize = 50
|
|
|
+ var mainTitle = "Mirai Console v0.01 Core v0.15"
|
|
|
+
|
|
|
+ override fun pushVersion(consoleVersion: String, consoleBuild: String, coreVersion: String) {
|
|
|
+ mainTitle = "Mirai Console(Terminal) $consoleVersion $consoleBuild Core $coreVersion"
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun pushLog(identity: Long, message: String) {
|
|
|
+ log[identity]!!.push(message)
|
|
|
+ if (identity == screens[currentScreenId]) {
|
|
|
+ drawLog(message)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun prePushBot(identity: Long) {
|
|
|
+ log[identity] = LimitLinkedQueue(cacheLogSize)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun pushBot(bot: Bot) {
|
|
|
+ botAdminCount[bot.uin] = 0
|
|
|
+ screens.add(bot.uin)
|
|
|
+ drawFrame(this.getScreenName(currentScreenId))
|
|
|
+ if (terminal is SwingTerminalFrame) {
|
|
|
+ terminal.flush()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var requesting = false
|
|
|
+ var requestResult: String? = null
|
|
|
+ override suspend fun requestInput(question: String): String {
|
|
|
+ requesting = true
|
|
|
+ while (requesting) {
|
|
|
+ delay(100)//不然会卡死 迷惑吧
|
|
|
+ }
|
|
|
+ return requestResult!!
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ suspend fun provideInput(input: String) {
|
|
|
+ if (requesting) {
|
|
|
+ requestResult = input
|
|
|
+ requesting = false
|
|
|
+ } else {
|
|
|
+ MiraiConsole.CommandProcessor.runConsoleCommand(commandBuilder.toString())
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ override fun pushBotAdminStatus(identity: Long, admins: List<Long>) {
|
|
|
+ botAdminCount[identity] = admins.size
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun createLoginSolver(): LoginSolver {
|
|
|
+ return object : LoginSolver() {
|
|
|
+ override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
|
|
|
+ val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
|
|
|
+ withContext(Dispatchers.IO) {
|
|
|
+ tempFile.createNewFile()
|
|
|
+ pushLog(0, "[Login Solver]需要图片验证码登录, 验证码为 4 字母")
|
|
|
+ try {
|
|
|
+ tempFile.writeChannel().apply {
|
|
|
+ writeFully(data)
|
|
|
+ close()
|
|
|
+ }
|
|
|
+ pushLog(0, "请查看文件 ${tempFile.absolutePath}")
|
|
|
+ } catch (e: Exception) {
|
|
|
+ error("[Login Solver]验证码无法保存[Error0001]")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var toLog = ""
|
|
|
+ tempFile.inputStream().use {
|
|
|
+ val img = ImageIO.read(it)
|
|
|
+ if (img == null) {
|
|
|
+ toLog += "无法创建字符图片. 请查看文件\n"
|
|
|
+ } else {
|
|
|
+ toLog += img.createCharImg((terminal.terminalSize.columns / 1.5).toInt())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ pushLog(0, "$toLog[Login Solver]请输验证码. ${tempFile.absolutePath}")
|
|
|
+ return requestInput("[Login Solver]请输入 4 位字母验证码. 若要更换验证码, 请直接回车")!!
|
|
|
+ .takeUnless { it.isEmpty() || it.length != 4 }
|
|
|
+ .also {
|
|
|
+ pushLog(0, "[Login Solver]正在提交[$it]中...")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
|
|
|
+ pushLog(0, "[Login Solver]需要滑动验证码")
|
|
|
+ pushLog(0, "[Login Solver]请在任意浏览器中打开以下链接并完成验证码. ")
|
|
|
+ pushLog(0, "[Login Solver]完成后请输入任意字符 ")
|
|
|
+ pushLog(0, url)
|
|
|
+ return requestInput("[Login Solver]完成后请输入任意字符").also {
|
|
|
+ pushLog(0, "[Login Solver]正在提交中")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
|
|
|
+ pushLog(0, "[Login Solver]需要进行账户安全认证")
|
|
|
+ pushLog(0, "[Login Solver]该账户有[设备锁]/[不常用登录地点]/[不常用设备登录]的问题")
|
|
|
+ pushLog(0, "[Login Solver]完成以下账号认证即可成功登录|理论本认证在mirai每个账户中最多出现1次")
|
|
|
+ pushLog(0, "[Login Solver]请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符")
|
|
|
+ pushLog(0, "[Login Solver]这步操作将在后续的版本中优化")
|
|
|
+ pushLog(0, url)
|
|
|
+ return requestInput("[Login Solver]完成后请输入任意字符").also {
|
|
|
+ pushLog(0, "[Login Solver]正在提交中...")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ val log = ConcurrentHashMap<Long, LimitLinkedQueue<String>>().also {
|
|
|
+ it[0L] = LimitLinkedQueue(cacheLogSize)
|
|
|
+ }
|
|
|
+
|
|
|
+ val botAdminCount = ConcurrentHashMap<Long, Int>()
|
|
|
+
|
|
|
+ private val screens = mutableListOf(0L)
|
|
|
+ private var currentScreenId = 0
|
|
|
+
|
|
|
+
|
|
|
+ lateinit var terminal: Terminal
|
|
|
+ lateinit var textGraphics: TextGraphics
|
|
|
+
|
|
|
+ var hasStart = false
|
|
|
+ private lateinit var internalPrinter: PrintStream
|
|
|
+ fun start() {
|
|
|
+ if (hasStart) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ internalPrinter = System.out
|
|
|
+
|
|
|
+
|
|
|
+ hasStart = true
|
|
|
+ val defaultTerminalFactory = DefaultTerminalFactory(internalPrinter, System.`in`, Charset.defaultCharset())
|
|
|
+ try {
|
|
|
+ terminal = defaultTerminalFactory.createTerminal()
|
|
|
+ terminal.enterPrivateMode()
|
|
|
+ terminal.clearScreen()
|
|
|
+ terminal.setCursorVisible(false)
|
|
|
+ } catch (e: Exception) {
|
|
|
+ try {
|
|
|
+ terminal = SwingTerminalFrame("Mirai Console")
|
|
|
+ terminal.enterPrivateMode()
|
|
|
+ terminal.clearScreen()
|
|
|
+ terminal.setCursorVisible(false)
|
|
|
+ } catch (e: Exception) {
|
|
|
+ error("can not create terminal")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ textGraphics = terminal.newTextGraphics()
|
|
|
+
|
|
|
+ /*
|
|
|
+ var lastRedrawTime = 0L
|
|
|
+ var lastNewWidth = 0
|
|
|
+ var lastNewHeight = 0
|
|
|
+
|
|
|
+ terminal.addResizeListener(TerminalResizeListener { terminal1: Terminal, newSize: TerminalSize ->
|
|
|
+ try {
|
|
|
+ if (lastNewHeight == newSize.rows
|
|
|
+ &&
|
|
|
+ lastNewWidth == newSize.columns
|
|
|
+ ) {
|
|
|
+ return@TerminalResizeListener
|
|
|
+ }
|
|
|
+ lastNewHeight = newSize.rows
|
|
|
+ lastNewWidth = newSize.columns
|
|
|
+ terminal.clearScreen()
|
|
|
+ if(terminal !is SwingTerminalFrame) {
|
|
|
+ Thread.sleep(300)
|
|
|
+ }
|
|
|
+ update()
|
|
|
+ redrawCommand()
|
|
|
+ redrawLogs(log[screens[currentScreenId]]!!)
|
|
|
+ }catch (ignored:Exception){
|
|
|
+
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ */
|
|
|
+ var lastJob: Job? = null
|
|
|
+ terminal.addResizeListener(TerminalResizeListener { terminal1: Terminal, newSize: TerminalSize ->
|
|
|
+ lastJob = GlobalScope.launch {
|
|
|
+ try {
|
|
|
+ delay(300)
|
|
|
+ if (lastJob == coroutineContext[Job]) {
|
|
|
+ terminal.clearScreen()
|
|
|
+ //inited = false
|
|
|
+ update()
|
|
|
+ redrawCommand()
|
|
|
+ redrawLogs(log[screens[currentScreenId]]!!)
|
|
|
+ }
|
|
|
+ } catch (e: Exception) {
|
|
|
+ pushLog(0, "[UI ERROR] ${e.message}")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ if (terminal !is SwingTerminalFrame) {
|
|
|
+ System.setOut(PrintStream(object : OutputStream() {
|
|
|
+ var builder = java.lang.StringBuilder()
|
|
|
+ override fun write(b: Int) {
|
|
|
+ with(b.toChar()) {
|
|
|
+ if (this == '\n') {
|
|
|
+ pushLog(0, builder.toString())
|
|
|
+ builder = java.lang.StringBuilder()
|
|
|
+ } else {
|
|
|
+ builder.append(this)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }))
|
|
|
+ }
|
|
|
+
|
|
|
+ System.setErr(System.out)
|
|
|
+
|
|
|
+ try {
|
|
|
+ update()
|
|
|
+ } catch (e: Exception) {
|
|
|
+ pushLog(0, "[UI ERROR] ${e.message}")
|
|
|
+ }
|
|
|
+
|
|
|
+ val charList = listOf(',', '.', '/', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '=', '+', '!', ' ')
|
|
|
+ thread {
|
|
|
+ while (true) {
|
|
|
+ try {
|
|
|
+ var keyStroke: KeyStroke = terminal.readInput()
|
|
|
+
|
|
|
+ when (keyStroke.keyType) {
|
|
|
+ KeyType.ArrowLeft -> {
|
|
|
+ currentScreenId =
|
|
|
+ getLeftScreenId()
|
|
|
+ clearRows(2)
|
|
|
+ cleanPage()
|
|
|
+ update()
|
|
|
+ }
|
|
|
+ KeyType.ArrowRight -> {
|
|
|
+ currentScreenId =
|
|
|
+ getRightScreenId()
|
|
|
+ clearRows(2)
|
|
|
+ cleanPage()
|
|
|
+ update()
|
|
|
+ }
|
|
|
+ KeyType.Enter -> {
|
|
|
+ runBlocking {
|
|
|
+ provideInput(commandBuilder.toString())
|
|
|
+ }
|
|
|
+ emptyCommand()
|
|
|
+ }
|
|
|
+ KeyType.Escape -> {
|
|
|
+ exit()
|
|
|
+ }
|
|
|
+ else -> {
|
|
|
+ if (keyStroke.character != null) {
|
|
|
+ if (keyStroke.character.toInt() == 8) {
|
|
|
+ deleteCommandChar()
|
|
|
+ }
|
|
|
+ if (keyStroke.character.isLetterOrDigit() || charList.contains(keyStroke.character)) {
|
|
|
+ addCommandChar(keyStroke.character)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e: Exception) {
|
|
|
+ pushLog(0, "[UI ERROR] ${e.message}")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun getLeftScreenId(): Int {
|
|
|
+ var newId = currentScreenId - 1
|
|
|
+ if (newId < 0) {
|
|
|
+ newId = screens.size - 1
|
|
|
+ }
|
|
|
+ return newId
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun getRightScreenId(): Int {
|
|
|
+ var newId = 1 + currentScreenId
|
|
|
+ if (newId >= screens.size) {
|
|
|
+ newId = 0
|
|
|
+ }
|
|
|
+ return newId
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun getScreenName(id: Int): String {
|
|
|
+ return when (screens[id]) {
|
|
|
+ 0L -> {
|
|
|
+ "Console Screen"
|
|
|
+ }
|
|
|
+ else -> {
|
|
|
+ "Bot: ${screens[id]}"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ fun clearRows(row: Int) {
|
|
|
+ textGraphics.putString(
|
|
|
+ 0, row, " ".repeat(
|
|
|
+ terminal.terminalSize.columns
|
|
|
+ )
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ fun drawFrame(
|
|
|
+ title: String
|
|
|
+ ) {
|
|
|
+ val width = terminal.terminalSize.columns
|
|
|
+ val height = terminal.terminalSize.rows
|
|
|
+ terminal.setBackgroundColor(TextColor.ANSI.DEFAULT)
|
|
|
+
|
|
|
+ textGraphics.foregroundColor = TextColor.ANSI.WHITE
|
|
|
+ textGraphics.backgroundColor = TextColor.ANSI.GREEN
|
|
|
+ textGraphics.putString((width - mainTitle.actualLength()) / 2, 1, mainTitle, SGR.BOLD)
|
|
|
+ textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ textGraphics.putString(2, 3, "-".repeat(width - 4))
|
|
|
+ textGraphics.putString(2, 5, "-".repeat(width - 4))
|
|
|
+ textGraphics.putString(2, height - 4, "-".repeat(width - 4))
|
|
|
+ textGraphics.putString(2, height - 3, "|>>>")
|
|
|
+ textGraphics.putString(width - 3, height - 3, "|")
|
|
|
+ textGraphics.putString(2, height - 2, "-".repeat(width - 4))
|
|
|
+
|
|
|
+ textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ val leftName =
|
|
|
+ getScreenName(getLeftScreenId())
|
|
|
+ // clearRows(2)
|
|
|
+ textGraphics.putString((width - title.actualLength()) / 2 - "$leftName << ".length, 2, "$leftName << ")
|
|
|
+ textGraphics.foregroundColor = TextColor.ANSI.WHITE
|
|
|
+ textGraphics.backgroundColor = TextColor.ANSI.YELLOW
|
|
|
+ textGraphics.putString((width - title.actualLength()) / 2, 2, title, SGR.BOLD)
|
|
|
+ textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ val rightName =
|
|
|
+ getScreenName(getRightScreenId())
|
|
|
+ textGraphics.putString((width + title.actualLength()) / 2 + 1, 2, ">> $rightName")
|
|
|
+ }
|
|
|
+
|
|
|
+ fun drawMainFrame(
|
|
|
+ onlineBotCount: Number
|
|
|
+ ) {
|
|
|
+ drawFrame("Console Screen")
|
|
|
+ val width = terminal.terminalSize.columns
|
|
|
+ textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ clearRows(4)
|
|
|
+ textGraphics.putString(2, 4, "|Online Bots: $onlineBotCount")
|
|
|
+ textGraphics.putString(
|
|
|
+ width - 2 - "Powered By Mamoe Technologies|".actualLength(),
|
|
|
+ 4,
|
|
|
+ "Powered By Mamoe Technologies|"
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ fun drawBotFrame(
|
|
|
+ qq: Long,
|
|
|
+ adminCount: Number
|
|
|
+ ) {
|
|
|
+ drawFrame("Bot: $qq")
|
|
|
+ val width = terminal.terminalSize.columns
|
|
|
+ textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ clearRows(4)
|
|
|
+ textGraphics.putString(2, 4, "|Admins: $adminCount")
|
|
|
+ textGraphics.putString(width - 2 - "Add admins via commands|".actualLength(), 4, "Add admins via commands|")
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ object LoggerDrawer {
|
|
|
+ var currentHeight = 6
|
|
|
+
|
|
|
+ fun drawLog(string: String, flush: Boolean = true) {
|
|
|
+ val maxHeight = terminal.terminalSize.rows - 4
|
|
|
+ val heightNeed = (string.actualLength() / (terminal.terminalSize.columns - 6)) + 1
|
|
|
+ if (heightNeed - 1 > maxHeight) {
|
|
|
+ pushLog(0, "[UI ERROR]: 您的屏幕太小, 有一条超长LOG无法显示")
|
|
|
+ return//拒绝打印
|
|
|
+ }
|
|
|
+ if (currentHeight + heightNeed > maxHeight) {
|
|
|
+ cleanPage()//翻页
|
|
|
+ }
|
|
|
+ if (string.contains("\n")) {
|
|
|
+ string.split("\n").forEach {
|
|
|
+ drawLog(string, false)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ val width = terminal.terminalSize.columns - 6
|
|
|
+ var x = string
|
|
|
+ while (true) {
|
|
|
+ if (x == "") {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ val toWrite = if (x.actualLength() > width) {
|
|
|
+ val index = x.getSubStringIndexByActualLength(width)
|
|
|
+ x.substring(0, index).also {
|
|
|
+ x = if (index < x.length) {
|
|
|
+ x.substring(index)
|
|
|
+ } else {
|
|
|
+ ""
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ x.also {
|
|
|
+ x = ""
|
|
|
+ }
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ textGraphics.foregroundColor = TextColor.ANSI.GREEN
|
|
|
+ textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ textGraphics.putString(
|
|
|
+ 3,
|
|
|
+ currentHeight, toWrite, SGR.ITALIC
|
|
|
+ )
|
|
|
+ } catch (ignored: Exception) {
|
|
|
+ //
|
|
|
+ }
|
|
|
+ ++currentHeight
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (flush && terminal is SwingTerminalFrame) {
|
|
|
+ terminal.flush()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ fun cleanPage() {
|
|
|
+ for (index in 6 until terminal.terminalSize.rows - 4) {
|
|
|
+ clearRows(index)
|
|
|
+ }
|
|
|
+ currentHeight = 6
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ fun redrawLogs(toDraw: Queue<String>) {
|
|
|
+ //this.cleanPage()
|
|
|
+ currentHeight = 6
|
|
|
+ var logsToDraw = 0
|
|
|
+ var vara = 0
|
|
|
+ val toPrint = mutableListOf<String>()
|
|
|
+ toDraw.forEach {
|
|
|
+ val heightNeed = (it.actualLength() / (terminal.terminalSize.columns - 6)) + 1
|
|
|
+ vara += heightNeed
|
|
|
+ if (currentHeight + vara < terminal.terminalSize.rows - 4) {
|
|
|
+ logsToDraw++
|
|
|
+ toPrint.add(it)
|
|
|
+ } else {
|
|
|
+ return@forEach
|
|
|
+ }
|
|
|
+ }
|
|
|
+ toPrint.reversed().forEach {
|
|
|
+ drawLog(it, false)
|
|
|
+ }
|
|
|
+ if (terminal is SwingTerminalFrame) {
|
|
|
+ terminal.flush()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ var commandBuilder = StringBuilder()
|
|
|
+ fun redrawCommand() {
|
|
|
+ val height = terminal.terminalSize.rows
|
|
|
+ val width = terminal.terminalSize.columns
|
|
|
+ clearRows(height - 3)
|
|
|
+ textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ textGraphics.putString(2, height - 3, "|>>>")
|
|
|
+ textGraphics.putString(width - 3, height - 3, "|")
|
|
|
+ textGraphics.foregroundColor = TextColor.ANSI.WHITE
|
|
|
+ textGraphics.backgroundColor = TextColor.ANSI.BLACK
|
|
|
+ textGraphics.putString(7, height - 3, commandBuilder.toString())
|
|
|
+ if (terminal is SwingTerminalFrame) {
|
|
|
+ terminal.flush()
|
|
|
+ }
|
|
|
+ textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun addCommandChar(
|
|
|
+ c: Char
|
|
|
+ ) {
|
|
|
+ if (!requesting && commandBuilder.isEmpty() && c != '/') {
|
|
|
+ addCommandChar('/')
|
|
|
+ }
|
|
|
+ textGraphics.foregroundColor = TextColor.ANSI.WHITE
|
|
|
+ textGraphics.backgroundColor = TextColor.ANSI.BLACK
|
|
|
+ val height = terminal.terminalSize.rows
|
|
|
+ commandBuilder.append(c)
|
|
|
+ if (terminal is SwingTerminalFrame) {
|
|
|
+ redrawCommand()
|
|
|
+ } else {
|
|
|
+ textGraphics.putString(6 + commandBuilder.length, height - 3, c.toString())
|
|
|
+ }
|
|
|
+ textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun deleteCommandChar() {
|
|
|
+ if (!commandBuilder.isEmpty()) {
|
|
|
+ commandBuilder = StringBuilder(commandBuilder.toString().substring(0, commandBuilder.length - 1))
|
|
|
+ }
|
|
|
+ val height = terminal.terminalSize.rows
|
|
|
+ if (terminal is SwingTerminalFrame) {
|
|
|
+ redrawCommand()
|
|
|
+ } else {
|
|
|
+ textGraphics.putString(7 + commandBuilder.length, height - 3, " ")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ var lastEmpty: Job? = null
|
|
|
+ private fun emptyCommand() {
|
|
|
+ commandBuilder = StringBuilder()
|
|
|
+ if (terminal is SwingTerminal) {
|
|
|
+ redrawCommand()
|
|
|
+ terminal.flush()
|
|
|
+ } else {
|
|
|
+ lastEmpty = GlobalScope.launch {
|
|
|
+ try {
|
|
|
+ delay(100)
|
|
|
+ if (lastEmpty == coroutineContext[Job]) {
|
|
|
+ terminal.clearScreen()
|
|
|
+ //inited = false
|
|
|
+ update()
|
|
|
+ redrawCommand()
|
|
|
+ redrawLogs(log[screens[currentScreenId]]!!)
|
|
|
+ }
|
|
|
+ } catch (e: Exception) {
|
|
|
+ pushLog(0, "[UI ERROR] ${e.message}")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fun update() {
|
|
|
+ when (screens[currentScreenId]) {
|
|
|
+ 0L -> {
|
|
|
+ drawMainFrame(screens.size - 1)
|
|
|
+ }
|
|
|
+ else -> {
|
|
|
+ drawBotFrame(
|
|
|
+ screens[currentScreenId],
|
|
|
+ 0
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ redrawLogs(log[screens[currentScreenId]]!!)
|
|
|
+ }
|
|
|
+
|
|
|
+ fun exit() {
|
|
|
+ try {
|
|
|
+ terminal.exitPrivateMode()
|
|
|
+ terminal.close()
|
|
|
+ exitProcess(0)
|
|
|
+ } catch (ignored: Exception) {
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+class LimitLinkedQueue<T>(
|
|
|
+ val limit: Int = 50
|
|
|
+) : ConcurrentLinkedDeque<T>() {
|
|
|
+ override fun push(e: T) {
|
|
|
+ if (size >= limit) {
|
|
|
+ this.pollLast()
|
|
|
+ }
|
|
|
+ return super.push(e)
|
|
|
+ }
|
|
|
+}
|