MISC: change all indent to 2 spaces (so compat!)

This commit is contained in:
imkiva 2021-05-08 00:11:00 +08:00 committed by Kiva Oyama
parent 880082458a
commit e8e9162fcf
326 changed files with 37875 additions and 38821 deletions

View File

@ -2,8 +2,12 @@
## Our Pledge ## Our Pledge
Originally, NeoTerm was designed as the front end of [Termux](https://github.com/termux/termux-app) to provide some functions that Termux didn't have, but we found it very convenient. In continuous development, we discovered our goal: to be the best terminal for Android. Originally, NeoTerm was designed as the front end of [Termux](https://github.com/termux/termux-app) to provide some
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. functions that Termux didn't have, but we found it very convenient. In continuous development, we discovered our goal:
to be the best terminal for Android. In the interest of fostering an open and welcoming environment, we as contributors
and maintainers pledge to making participation in our project and our community a harassment-free experience for
everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards ## Our Standards
@ -26,23 +30,35 @@ Examples of unacceptable behavior by participants include:
## Our Responsibilities ## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take
appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits,
issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any
contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope ## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the
project or its community. Examples of representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed representative at an online or offline
event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at kiva515@foxmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at
kiva515@foxmail.com. The project team will review and investigate all complaints, and will respond in a way that it
deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the
reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent
repercussions as determined by other members of the project's leadership.
## Attribution ## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available
at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org [homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/ [version]: http://contributor-covenant.org/version/1/4/

View File

@ -1,5 +1,6 @@
# Contributing # Contributing
# How to # How to
NeoTerm is a free software, so any contributions and any contribution types are welcomed! NeoTerm is a free software, so any contributions and any contribution types are welcomed!

View File

@ -2,31 +2,31 @@ apply plugin: 'java-library'
apply plugin: 'kotlin' apply plugin: 'kotlin'
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
compile rootProject.ext.deps["kotlin-stdlib"] compile rootProject.ext.deps["kotlin-stdlib"]
} }
buildscript { buildscript {
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath rootProject.ext.deps["kotlin-gradle-plugin"] classpath rootProject.ext.deps["kotlin-gradle-plugin"]
} }
} }
repositories { repositories {
mavenCentral() mavenCentral()
} }
compileKotlin { compileKotlin {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
} }
} }
compileTestKotlin { compileTestKotlin {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
} }
} }
dependencies { dependencies {
testImplementation rootProject.ext.deps["junit"] testImplementation rootProject.ext.deps["junit"]
} }

View File

@ -5,9 +5,9 @@ package io.neolang.ast
*/ */
open class NeoLangToken(val tokenType: NeoLangTokenType, val tokenValue: NeoLangTokenValue) { open class NeoLangToken(val tokenType: NeoLangTokenType, val tokenValue: NeoLangTokenValue) {
var lineNumber = 0 var lineNumber = 0
override fun toString(): String { override fun toString(): String {
return "Token { tokenType: $tokenType, tokenValue: $tokenValue };" return "Token { tokenType: $tokenType, tokenValue: $tokenValue };"
} }
} }

View File

@ -5,15 +5,15 @@ package io.neolang.ast
*/ */
enum class NeoLangTokenType { enum class NeoLangTokenType {
NUMBER, NUMBER,
ID, ID,
STRING, STRING,
BRACKET_START, BRACKET_START,
BRACKET_END, BRACKET_END,
ARRAY_START, ARRAY_START,
ARRAY_END, ARRAY_END,
COLON, COLON,
COMMA, COMMA,
EOL, EOL,
EOF, EOF,
} }

View File

@ -7,32 +7,32 @@ import io.neolang.runtime.type.NeoLangValue
*/ */
class NeoLangTokenValue(val value: NeoLangValue) { class NeoLangTokenValue(val value: NeoLangValue) {
override fun toString(): String { override fun toString(): String {
return value.asString() return value.asString()
} }
companion object { companion object {
val COLON = NeoLangTokenValue(NeoLangValue(":")) val COLON = NeoLangTokenValue(NeoLangValue(":"))
val COMMA = NeoLangTokenValue(NeoLangValue(",")) val COMMA = NeoLangTokenValue(NeoLangValue(","))
val QUOTE = NeoLangTokenValue(NeoLangValue("\"")) val QUOTE = NeoLangTokenValue(NeoLangValue("\""))
val EOF = NeoLangTokenValue(NeoLangValue("<EOF>")) val EOF = NeoLangTokenValue(NeoLangValue("<EOF>"))
val BRACKET_START = NeoLangTokenValue(NeoLangValue("{")) val BRACKET_START = NeoLangTokenValue(NeoLangValue("{"))
val BRACKET_END = NeoLangTokenValue(NeoLangValue("}")) val BRACKET_END = NeoLangTokenValue(NeoLangValue("}"))
val ARRAY_START = NeoLangTokenValue(NeoLangValue("[")) val ARRAY_START = NeoLangTokenValue(NeoLangValue("["))
val ARRAY_END = NeoLangTokenValue(NeoLangValue("]")) val ARRAY_END = NeoLangTokenValue(NeoLangValue("]"))
fun wrap(tokenText: String): NeoLangTokenValue { fun wrap(tokenText: String): NeoLangTokenValue {
return when (tokenText) { return when (tokenText) {
COLON.value.asString() -> COLON COLON.value.asString() -> COLON
COMMA.value.asString() -> COMMA COMMA.value.asString() -> COMMA
QUOTE.value.asString() -> QUOTE QUOTE.value.asString() -> QUOTE
BRACKET_START.value.asString() -> BRACKET_START BRACKET_START.value.asString() -> BRACKET_START
BRACKET_END.value.asString() -> BRACKET_END BRACKET_END.value.asString() -> BRACKET_END
ARRAY_START.value.asString() -> ARRAY_START ARRAY_START.value.asString() -> ARRAY_START
ARRAY_END.value.asString() -> ARRAY_END ARRAY_END.value.asString() -> ARRAY_END
else -> NeoLangTokenValue(NeoLangValue(tokenText)) else -> NeoLangTokenValue(NeoLangValue(tokenText))
} }
}
} }
}
} }

View File

@ -6,7 +6,7 @@ import io.neolang.ast.visitor.VisitorFactory
* @author kiva * @author kiva
*/ */
open class NeoLangAst { open class NeoLangAst {
fun visit(): VisitorFactory { fun visit(): VisitorFactory {
return VisitorFactory(this) return VisitorFactory(this)
} }
} }

View File

@ -6,7 +6,7 @@ import io.neolang.ast.base.NeoLangBaseNode
* @author kiva * @author kiva
*/ */
class NeoLangArrayNode(val arrayNameNode: NeoLangStringNode, val elements: Array<ArrayElement>) : NeoLangBaseNode() { class NeoLangArrayNode(val arrayNameNode: NeoLangStringNode, val elements: Array<ArrayElement>) : NeoLangBaseNode() {
companion object { companion object {
class ArrayElement(val index: Int, val block: NeoLangBlockNode) class ArrayElement(val index: Int, val block: NeoLangBlockNode)
} }
} }

View File

@ -6,7 +6,7 @@ import io.neolang.ast.base.NeoLangBaseNode
* @author kiva * @author kiva
*/ */
open class NeoLangAstBasedNode(val ast: NeoLangBaseNode) : NeoLangBaseNode() { open class NeoLangAstBasedNode(val ast: NeoLangBaseNode) : NeoLangBaseNode() {
override fun toString(): String { override fun toString(): String {
return "${javaClass.simpleName} { ast: $ast }" return "${javaClass.simpleName} { ast: $ast }"
} }
} }

View File

@ -7,7 +7,7 @@ import io.neolang.ast.base.NeoLangBaseNode
*/ */
class NeoLangAttributeNode(val stringNode: NeoLangStringNode, val blockNode: NeoLangBlockNode) : NeoLangBaseNode() { class NeoLangAttributeNode(val stringNode: NeoLangStringNode, val blockNode: NeoLangBlockNode) : NeoLangBaseNode() {
override fun toString(): String { override fun toString(): String {
return "NeoLangAttributeNode { stringNode: $stringNode, block: $blockNode }" return "NeoLangAttributeNode { stringNode: $stringNode, block: $blockNode }"
} }
} }

View File

@ -6,9 +6,9 @@ import io.neolang.ast.base.NeoLangBaseNode
* @author kiva * @author kiva
*/ */
class NeoLangBlockNode(blockElement: NeoLangBaseNode) : NeoLangAstBasedNode(blockElement) { class NeoLangBlockNode(blockElement: NeoLangBaseNode) : NeoLangAstBasedNode(blockElement) {
companion object { companion object {
fun emptyNode(): NeoLangBlockNode { fun emptyNode(): NeoLangBlockNode {
return NeoLangBlockNode(NeoLangDummyNode()) return NeoLangBlockNode(NeoLangDummyNode())
}
} }
}
} }

View File

@ -7,13 +7,13 @@ import io.neolang.ast.base.NeoLangBaseNode
*/ */
class NeoLangGroupNode(val attributes: Array<NeoLangAttributeNode>) : NeoLangBaseNode() { class NeoLangGroupNode(val attributes: Array<NeoLangAttributeNode>) : NeoLangBaseNode() {
override fun toString(): String { override fun toString(): String {
return "NeoLangGroupNode { attrs: $attributes }" return "NeoLangGroupNode { attrs: $attributes }"
} }
companion object { companion object {
fun emptyNode() : NeoLangGroupNode { fun emptyNode(): NeoLangGroupNode {
return NeoLangGroupNode(arrayOf()) return NeoLangGroupNode(arrayOf())
}
} }
}
} }

View File

@ -8,14 +8,14 @@ import io.neolang.ast.base.NeoLangBaseNode
class NeoLangProgramNode(val groups: List<NeoLangGroupNode>) : NeoLangBaseNode() { class NeoLangProgramNode(val groups: List<NeoLangGroupNode>) : NeoLangBaseNode() {
override fun toString(): String { override fun toString(): String {
return "NeoLangProgramNode { groups: $groups }" return "NeoLangProgramNode { groups: $groups }"
} }
companion object { companion object {
fun emptyNode() : NeoLangProgramNode { fun emptyNode(): NeoLangProgramNode {
return NeoLangProgramNode(listOf()) return NeoLangProgramNode(listOf())
}
} }
}
} }

View File

@ -8,11 +8,11 @@ import io.neolang.runtime.type.NeoLangValue
* @author kiva * @author kiva
*/ */
open class NeoLangTokenBasedNode(val token: NeoLangToken) : NeoLangBaseNode() { open class NeoLangTokenBasedNode(val token: NeoLangToken) : NeoLangBaseNode() {
override fun toString(): String { override fun toString(): String {
return "${javaClass.simpleName} { token: $token }" return "${javaClass.simpleName} { token: $token }"
} }
fun eval(): NeoLangValue { fun eval(): NeoLangValue {
return token.tokenValue.value return token.tokenValue.value
} }
} }

View File

@ -6,12 +6,12 @@ import io.neolang.ast.base.NeoLangAst
* @author kiva * @author kiva
*/ */
class AstVisitor(private val ast: NeoLangAst, private val visitorCallback: IVisitorCallback) { class AstVisitor(private val ast: NeoLangAst, private val visitorCallback: IVisitorCallback) {
fun start() { fun start() {
AstVisitorImpl.visitStartAst(ast, visitorCallback) AstVisitorImpl.visitStartAst(ast, visitorCallback)
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <T : IVisitorCallback> getCallback() : T { fun <T : IVisitorCallback> getCallback(): T {
return visitorCallback as T return visitorCallback as T
} }
} }

View File

@ -18,87 +18,87 @@ import io.neolang.runtime.type.NeoLangValue
* @author kiva * @author kiva
*/ */
internal object AstVisitorImpl { internal object AstVisitorImpl {
fun visitProgram(ast: NeoLangProgramNode, visitorCallback: IVisitorCallback) { fun visitProgram(ast: NeoLangProgramNode, visitorCallback: IVisitorCallback) {
visitorCallback.onStart() visitorCallback.onStart()
ast.groups.forEach { visitGroup(it, visitorCallback) } ast.groups.forEach { visitGroup(it, visitorCallback) }
visitorCallback.onFinish() visitorCallback.onFinish()
}
fun visitGroup(ast: NeoLangGroupNode, visitorCallback: IVisitorCallback) {
ast.attributes.forEach {
visitAttribute(it, visitorCallback)
} }
}
fun visitGroup(ast: NeoLangGroupNode, visitorCallback: IVisitorCallback) { fun visitAttribute(ast: NeoLangAttributeNode, visitorCallback: IVisitorCallback) {
ast.attributes.forEach { visitBlock(ast.blockNode, ast.stringNode.eval().asString(), visitorCallback)
visitAttribute(it, visitorCallback) }
}
}
fun visitAttribute(ast: NeoLangAttributeNode, visitorCallback: IVisitorCallback) { fun visitArray(ast: NeoLangArrayNode, visitorCallback: IVisitorCallback) {
visitBlock(ast.blockNode, ast.stringNode.eval().asString(), visitorCallback) val arrayName = ast.arrayNameNode.eval().asString()
}
fun visitArray(ast: NeoLangArrayNode, visitorCallback: IVisitorCallback) { visitorCallback.onEnterContext(arrayName)
val arrayName = ast.arrayNameNode.eval().asString() ast.elements.forEach {
AstVisitorImpl.visitArrayElementBlock(it.block, it.index, visitorCallback)
visitorCallback.onEnterContext(arrayName)
ast.elements.forEach {
AstVisitorImpl.visitArrayElementBlock(it.block, it.index, visitorCallback)
// AstVisitorImpl.visitBlock(it.block, it.index.toString(), visitorCallback) // AstVisitorImpl.visitBlock(it.block, it.index.toString(), visitorCallback)
} }
visitorCallback.onExitContext()
}
fun visitArrayElementBlock(ast: NeoLangBlockNode, index: Int, visitorCallback: IVisitorCallback) {
val visitingNode = ast.ast
when (visitingNode) {
is NeoLangGroupNode -> {
// is a sub block, e.g.
// block: { $blockName: {} }
visitorCallback.onEnterContext(index.toString())
AstVisitorImpl.visitGroup(visitingNode, visitorCallback)
visitorCallback.onExitContext() visitorCallback.onExitContext()
}
is NeoLangStringNode -> {
definePrimaryData(index.toString(), visitingNode.eval(), visitorCallback)
}
is NeoLangNumberNode -> {
definePrimaryData(index.toString(), visitingNode.eval(), visitorCallback)
}
} }
}
fun visitArrayElementBlock(ast: NeoLangBlockNode, index: Int, visitorCallback: IVisitorCallback) { fun visitBlock(ast: NeoLangBlockNode, blockName: String, visitorCallback: IVisitorCallback) {
val visitingNode = ast.ast val visitingNode = ast.ast
when (visitingNode) { when (visitingNode) {
is NeoLangGroupNode -> { is NeoLangGroupNode -> {
// is a sub block, e.g. // is a sub block, e.g.
// block: { $blockName: {} } // block: { $blockName: {} }
visitorCallback.onEnterContext(index.toString())
AstVisitorImpl.visitGroup(visitingNode, visitorCallback) visitorCallback.onEnterContext(blockName)
visitorCallback.onExitContext() AstVisitorImpl.visitGroup(visitingNode, visitorCallback)
} visitorCallback.onExitContext()
is NeoLangStringNode -> { }
definePrimaryData(index.toString(), visitingNode.eval(), visitorCallback) is NeoLangArrayNode -> {
} // array: [ "a", "b", "c", 1, 2, 3 ]
is NeoLangNumberNode -> { AstVisitorImpl.visitArray(visitingNode, visitorCallback)
definePrimaryData(index.toString(), visitingNode.eval(), visitorCallback) }
} is NeoLangStringNode -> {
} // block: { $blockName: "hello" }
definePrimaryData(blockName, visitingNode.eval(), visitorCallback)
}
is NeoLangNumberNode -> {
// block: { $blockName: 123.456 }
definePrimaryData(blockName, visitingNode.eval(), visitorCallback)
}
} }
}
fun visitBlock(ast: NeoLangBlockNode, blockName: String, visitorCallback: IVisitorCallback) { private fun definePrimaryData(name: String, value: NeoLangValue, visitorCallback: IVisitorCallback) {
val visitingNode = ast.ast visitorCallback.getCurrentContext().defineAttribute(name, value)
when (visitingNode) { }
is NeoLangGroupNode -> {
// is a sub block, e.g.
// block: { $blockName: {} }
visitorCallback.onEnterContext(blockName) fun visitStartAst(ast: NeoLangAst, visitorCallback: IVisitorCallback) {
AstVisitorImpl.visitGroup(visitingNode, visitorCallback) when (ast) {
visitorCallback.onExitContext() is NeoLangProgramNode -> AstVisitorImpl.visitProgram(ast, visitorCallback)
} is NeoLangGroupNode -> AstVisitorImpl.visitGroup(ast, visitorCallback)
is NeoLangArrayNode -> { is NeoLangArrayNode -> AstVisitorImpl.visitArray(ast, visitorCallback)
// array: [ "a", "b", "c", 1, 2, 3 ]
AstVisitorImpl.visitArray(visitingNode, visitorCallback)
}
is NeoLangStringNode -> {
// block: { $blockName: "hello" }
definePrimaryData(blockName, visitingNode.eval(), visitorCallback)
}
is NeoLangNumberNode -> {
// block: { $blockName: 123.456 }
definePrimaryData(blockName, visitingNode.eval(), visitorCallback)
}
}
}
private fun definePrimaryData(name: String, value: NeoLangValue, visitorCallback: IVisitorCallback) {
visitorCallback.getCurrentContext().defineAttribute(name, value)
}
fun visitStartAst(ast: NeoLangAst, visitorCallback: IVisitorCallback) {
when (ast) {
is NeoLangProgramNode -> AstVisitorImpl.visitProgram(ast, visitorCallback)
is NeoLangGroupNode -> AstVisitorImpl.visitGroup(ast, visitorCallback)
is NeoLangArrayNode -> AstVisitorImpl.visitArray(ast, visitorCallback)
}
} }
}
} }

View File

@ -6,13 +6,13 @@ import io.neolang.runtime.context.NeoLangContext
* @author kiva * @author kiva
*/ */
interface IVisitorCallback { interface IVisitorCallback {
fun onStart() fun onStart()
fun onFinish() fun onFinish()
fun onEnterContext(contextName: String) fun onEnterContext(contextName: String)
fun onExitContext() fun onExitContext()
fun getCurrentContext() : NeoLangContext fun getCurrentContext(): NeoLangContext
} }

View File

@ -6,19 +6,19 @@ import io.neolang.runtime.context.NeoLangContext
* @author kiva * @author kiva
*/ */
open class IVisitorCallbackAdapter : IVisitorCallback { open class IVisitorCallbackAdapter : IVisitorCallback {
override fun onStart() { override fun onStart() {
} }
override fun onFinish() { override fun onFinish() {
} }
override fun onEnterContext(contextName: String) { override fun onEnterContext(contextName: String) {
} }
override fun onExitContext() { override fun onExitContext() {
} }
override fun getCurrentContext(): NeoLangContext { override fun getCurrentContext(): NeoLangContext {
throw RuntimeException("getCurrentContext() not supported in this IVisitorCallback!") throw RuntimeException("getCurrentContext() not supported in this IVisitorCallback!")
} }
} }

View File

@ -8,11 +8,11 @@ import io.neolang.ast.base.NeoLangAst
class VisitorFactory(private val ast: NeoLangAst) { class VisitorFactory(private val ast: NeoLangAst) {
fun getVisitor(callbackInterface: Class<out IVisitorCallback>): AstVisitor? { fun getVisitor(callbackInterface: Class<out IVisitorCallback>): AstVisitor? {
try { try {
return AstVisitor(ast, callbackInterface.newInstance()) return AstVisitor(ast, callbackInterface.newInstance())
} catch (e: Exception) { } catch (e: Exception) {
return null return null
}
} }
}
} }

View File

@ -8,33 +8,33 @@ import java.io.FileInputStream
* @author kiva * @author kiva
*/ */
class Main { class Main {
companion object { companion object {
@JvmStatic @JvmStatic
fun main(args: Array<String>) { fun main(args: Array<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
println("Usage: NeoLang <program.nl>") println("Usage: NeoLang <program.nl>")
return return
} }
val parser = NeoLangParser() val parser = NeoLangParser()
args.forEach { args.forEach {
val programCode = readFully(it) val programCode = readFully(it)
parser.setInputSource(programCode) parser.setInputSource(programCode)
val ast = parser.parse() val ast = parser.parse()
println("Compile `$it'") println("Compile `$it'")
ast.visit() ast.visit()
.getVisitor(DisplayProcessVisitor::class.java) .getVisitor(DisplayProcessVisitor::class.java)
?.start() ?.start()
} }
return return
}
private fun readFully(file: String): String {
FileInputStream(file).use {
val bytes = ByteArray(it.available())
it.read(bytes)
return String(bytes)
}
}
} }
private fun readFully(file: String): String {
FileInputStream(file).use {
val bytes = ByteArray(it.available())
it.read(bytes)
return String(bytes)
}
}
}
} }

View File

@ -4,7 +4,6 @@ import io.neolang.ast.NeoLangEOFToken
import io.neolang.ast.NeoLangToken import io.neolang.ast.NeoLangToken
import io.neolang.ast.NeoLangTokenType import io.neolang.ast.NeoLangTokenType
import io.neolang.ast.NeoLangTokenValue import io.neolang.ast.NeoLangTokenValue
import java.util.*
/** /**
* grammar: [ * grammar: [
@ -19,254 +18,255 @@ import java.util.*
* @author kiva * @author kiva
*/ */
class NeoLangLexer { class NeoLangLexer {
private var programCode: String? = null private var programCode: String? = null
private var currentPosition: Int = 0 private var currentPosition: Int = 0
private var currentChar: Char = ' ' private var currentChar: Char = ' '
private var lineNumber = 0 private var lineNumber = 0
internal fun setInputSource(programCode: String?) { internal fun setInputSource(programCode: String?) {
this.programCode = programCode this.programCode = programCode
}
internal fun lex(): List<NeoLangToken> {
val programCode = this.programCode ?: return listOf()
val tokens = ArrayList<NeoLangToken>()
currentPosition = 0
lineNumber = 1
if (programCode.isNotEmpty()) {
currentChar = programCode[currentPosition]
while (currentPosition < programCode.length) {
val token = nextToken
if (token is NeoLangEOFToken) {
break
}
tokens.add(token)
}
}
return tokens
}
private fun moveToNextChar(eofThrow: Boolean = false): Boolean {
val programCode = this.programCode ?: return false
currentPosition++
if (currentPosition >= programCode.length) {
if (eofThrow) {
throw InvalidTokenException("Unexpected EOF near `$currentChar' in line $lineNumber")
}
return false
} else {
currentChar = programCode[currentPosition]
return true
}
}
private val nextToken: NeoLangToken
get() {
val programCode = this.programCode ?: return NeoLangEOFToken()
while (currentChar == ' '
|| currentChar == '\t'
|| currentChar == '\n'
|| currentChar == '\r'
) {
if (currentChar == '\n') {
++lineNumber
}
// Skip white chars
if (!moveToNextChar()) {
return NeoLangEOFToken()
}
}
if (currentPosition >= programCode.length) {
return NeoLangEOFToken()
}
val currentToken = NeoLangTokenValue.wrap(currentChar.toString())
val token: NeoLangToken = when (currentToken) {
NeoLangTokenValue.COLON -> {
moveToNextChar(eofThrow = true)
NeoLangToken(NeoLangTokenType.COLON, currentToken)
}
NeoLangTokenValue.BRACKET_START -> {
moveToNextChar(eofThrow = true)
NeoLangToken(NeoLangTokenType.BRACKET_START, currentToken)
}
NeoLangTokenValue.BRACKET_END -> {
moveToNextChar()
NeoLangToken(NeoLangTokenType.BRACKET_END, currentToken)
}
NeoLangTokenValue.ARRAY_START -> {
moveToNextChar()
NeoLangToken(NeoLangTokenType.ARRAY_START, currentToken)
}
NeoLangTokenValue.ARRAY_END -> {
moveToNextChar()
NeoLangToken(NeoLangTokenType.ARRAY_END, currentToken)
}
NeoLangTokenValue.COMMA -> {
moveToNextChar(eofThrow = true)
NeoLangToken(NeoLangTokenType.COMMA, currentToken)
}
NeoLangTokenValue.QUOTE -> {
NeoLangToken(NeoLangTokenType.STRING, NeoLangTokenValue.wrap(getNextTokenAsString()))
}
else -> {
if (currentChar.isNumber()) {
NeoLangToken(NeoLangTokenType.NUMBER, NeoLangTokenValue.wrap(getNextTokenAsNumber()))
} else if (isIdentifier(currentChar, true)) {
NeoLangToken(NeoLangTokenType.ID, NeoLangTokenValue.wrap(getNextTokenAsId()))
} else {
throw InvalidTokenException("Unexpected character near line $lineNumber: $currentChar")
}
}
}
token.lineNumber = lineNumber
return token
} }
internal fun lex(): List<NeoLangToken> { private fun getNextTokenAsString(): String {
val programCode = this.programCode ?: return listOf() // Skip start quote
val tokens = ArrayList<NeoLangToken>() // and a single quote is now allowed
currentPosition = 0 moveToNextChar(eofThrow = true)
lineNumber = 1 val builder = StringBuilder()
if (programCode.isNotEmpty()) { var loop = true
currentChar = programCode[currentPosition] while (loop && currentChar != NeoLangTokenValue.QUOTE.value.asString()[0]) {
// NeoLang does not support escaped char
while (currentPosition < programCode.length) {
val token = nextToken
if (token is NeoLangEOFToken) {
break
}
tokens.add(token)
}
}
return tokens
}
private fun moveToNextChar(eofThrow: Boolean = false): Boolean {
val programCode = this.programCode ?: return false
currentPosition++
if (currentPosition >= programCode.length) {
if (eofThrow) {
throw InvalidTokenException("Unexpected EOF near `$currentChar' in line $lineNumber")
}
return false
} else {
currentChar = programCode[currentPosition]
return true
}
}
private val nextToken: NeoLangToken
get() {
val programCode = this.programCode ?: return NeoLangEOFToken()
while (currentChar == ' '
|| currentChar == '\t'
|| currentChar == '\n'
|| currentChar == '\r') {
if (currentChar == '\n') {
++lineNumber
}
// Skip white chars
if (!moveToNextChar()) {
return NeoLangEOFToken()
}
}
if (currentPosition >= programCode.length) {
return NeoLangEOFToken()
}
val currentToken = NeoLangTokenValue.wrap(currentChar.toString())
val token: NeoLangToken = when (currentToken) {
NeoLangTokenValue.COLON -> {
moveToNextChar(eofThrow = true)
NeoLangToken(NeoLangTokenType.COLON, currentToken)
}
NeoLangTokenValue.BRACKET_START -> {
moveToNextChar(eofThrow = true)
NeoLangToken(NeoLangTokenType.BRACKET_START, currentToken)
}
NeoLangTokenValue.BRACKET_END -> {
moveToNextChar()
NeoLangToken(NeoLangTokenType.BRACKET_END, currentToken)
}
NeoLangTokenValue.ARRAY_START -> {
moveToNextChar()
NeoLangToken(NeoLangTokenType.ARRAY_START, currentToken)
}
NeoLangTokenValue.ARRAY_END -> {
moveToNextChar()
NeoLangToken(NeoLangTokenType.ARRAY_END, currentToken)
}
NeoLangTokenValue.COMMA -> {
moveToNextChar(eofThrow = true)
NeoLangToken(NeoLangTokenType.COMMA, currentToken)
}
NeoLangTokenValue.QUOTE -> {
NeoLangToken(NeoLangTokenType.STRING, NeoLangTokenValue.wrap(getNextTokenAsString()))
}
else -> {
if (currentChar.isNumber()) {
NeoLangToken(NeoLangTokenType.NUMBER, NeoLangTokenValue.wrap(getNextTokenAsNumber()))
} else if (isIdentifier(currentChar, true)) {
NeoLangToken(NeoLangTokenType.ID, NeoLangTokenValue.wrap(getNextTokenAsId()))
} else {
throw InvalidTokenException("Unexpected character near line $lineNumber: $currentChar")
}
}
}
token.lineNumber = lineNumber
return token
}
private fun getNextTokenAsString(): String {
// Skip start quote
// and a single quote is now allowed
moveToNextChar(eofThrow = true)
val builder = StringBuilder()
var loop = true
while (loop && currentChar != NeoLangTokenValue.QUOTE.value.asString()[0]) {
// NeoLang does not support escaped char
// if (currentChar == '\\') { // if (currentChar == '\\') {
// builder.append('\\') // builder.append('\\')
// moveToNextChar(eofThrow = true) // moveToNextChar(eofThrow = true)
// } // }
builder.append(currentChar) builder.append(currentChar)
loop = moveToNextChar() loop = moveToNextChar()
}
// Skip end quote
moveToNextChar()
return builder.toString()
} }
private fun getNextTokenAsNumber(): String { // Skip end quote
var numberValue: Double = (currentChar.toInt() - '0'.toInt()).toDouble() moveToNextChar()
return builder.toString()
}
// Four types of numbers are supported: private fun getNextTokenAsNumber(): String {
// Dec(123) Hex(0x123) Oct(017) Bin(0b11) var numberValue: Double = (currentChar.toInt() - '0'.toInt()).toDouble()
// Dec // Four types of numbers are supported:
if (numberValue > 0) { // Dec(123) Hex(0x123) Oct(017) Bin(0b11)
numberValue = getNextDecimalNumber(numberValue)
} else {
// is 0
if (!moveToNextChar()) {
return numberValue.toString()
}
// Hex
if (currentChar == 'x' || currentChar == 'X') {
numberValue = getNextHexNumber(numberValue)
} else if (currentChar == 'b' || currentChar == 'B') {
numberValue = getNextBinaryNumber(numberValue)
} else {
numberValue = getNextOctalNumber(numberValue)
}
}
// Dec
if (numberValue > 0) {
numberValue = getNextDecimalNumber(numberValue)
} else {
// is 0
if (!moveToNextChar()) {
return numberValue.toString() return numberValue.toString()
}
// Hex
if (currentChar == 'x' || currentChar == 'X') {
numberValue = getNextHexNumber(numberValue)
} else if (currentChar == 'b' || currentChar == 'B') {
numberValue = getNextBinaryNumber(numberValue)
} else {
numberValue = getNextOctalNumber(numberValue)
}
} }
private fun getNextOctalNumber(numberValue: Double): Double { return numberValue.toString()
var value: Double = numberValue }
var loop = true
while (loop && currentChar in ('0'..'7')) { private fun getNextOctalNumber(numberValue: Double): Double {
value = value * 8 + currentChar.toNumber() var value: Double = numberValue
loop = moveToNextChar() var loop = true
while (loop && currentChar in ('0'..'7')) {
value = value * 8 + currentChar.toNumber()
loop = moveToNextChar()
}
return value
}
private fun getNextBinaryNumber(numberValue: Double): Double {
var value = numberValue
var loop = moveToNextChar() // skip 'b' or 'B'
while (loop && currentChar in ('0'..'1')) {
value += value * 2 + currentChar.toNumber()
loop = moveToNextChar()
}
return value
}
private fun getNextHexNumber(numberValue: Double): Double {
var value = numberValue
var loop = moveToNextChar() // skip 'x' or 'X'
while (loop && (currentChar.isHexNumber())) {
value *= 16
+(currentChar.toInt().and(15))
+if (currentChar >= 'A') 9 else 0
loop = moveToNextChar()
}
return value
}
private fun getNextDecimalNumber(numberValue: Double): Double {
var floatPointMeet = false
var floatPart: Double = 0.0
var floatNumberCounter = 1
var value = numberValue
var loop = moveToNextChar()
while (loop) {
if (currentChar.isNumber()) {
if (floatPointMeet) {
floatPart = floatPart * 10 + currentChar.toNumber()
floatNumberCounter *= 10
} else {
value = value * 10 + currentChar.toNumber()
} }
return value loop = moveToNextChar()
}
private fun getNextBinaryNumber(numberValue: Double): Double { } else if (currentChar == '.') {
var value = numberValue floatPointMeet = true
var loop = moveToNextChar() // skip 'b' or 'B' loop = moveToNextChar()
while (loop && currentChar in ('0'..'1')) { } else {
value += value * 2 + currentChar.toNumber() break
loop = moveToNextChar() }
}
return value + floatPart / floatNumberCounter
}
private fun getNextTokenAsId(): String {
return buildString {
while (isIdentifier(currentChar, false)) {
append(currentChar)
if (!moveToNextChar()) {
break
} }
return value }
} }
}
private fun getNextHexNumber(numberValue: Double): Double { private fun isIdentifier(tokenChar: Char, isFirstChar: Boolean): Boolean {
var value = numberValue val isId = (tokenChar in 'a'..'z')
var loop = moveToNextChar() // skip 'x' or 'X' || (tokenChar in 'A'..'Z')
while (loop && (currentChar.isHexNumber())) { || ("_-#$".contains(tokenChar))
value *= 16 return if (isFirstChar) isId else (isId || (tokenChar in '0'..'9'))
+ (currentChar.toInt().and(15)) }
+ if (currentChar >= 'A') 9 else 0
loop = moveToNextChar()
}
return value
}
private fun getNextDecimalNumber(numberValue: Double): Double { private fun Char.toNumber(): Int {
var floatPointMeet = false return if (isNumber()) {
var floatPart: Double = 0.0 this.toInt() - '0'.toInt()
var floatNumberCounter = 1 } else 0
var value = numberValue }
var loop = moveToNextChar() private fun Char.isNumber(): Boolean {
while (loop) { return this in ('0'..'9')
if (currentChar.isNumber()) { }
if (floatPointMeet) {
floatPart = floatPart * 10 + currentChar.toNumber()
floatNumberCounter *= 10
} else {
value = value * 10 + currentChar.toNumber()
}
loop = moveToNextChar()
} else if (currentChar == '.') { private fun Char.isHexNumber(): Boolean {
floatPointMeet = true return this.isNumber()
loop = moveToNextChar() || this in ('a'..'f')
} else { || this in ('A'..'F')
break }
}
}
return value + floatPart / floatNumberCounter
}
private fun getNextTokenAsId(): String {
return buildString {
while (isIdentifier(currentChar, false)) {
append(currentChar)
if (!moveToNextChar()) {
break
}
}
}
}
private fun isIdentifier(tokenChar: Char, isFirstChar: Boolean): Boolean {
val isId = (tokenChar in 'a'..'z')
|| (tokenChar in 'A'..'Z')
|| ("_-#$".contains(tokenChar))
return if (isFirstChar) isId else (isId || (tokenChar in '0'..'9'))
}
private fun Char.toNumber(): Int {
return if (isNumber()) {
this.toInt() - '0'.toInt()
} else 0
}
private fun Char.isNumber(): Boolean {
return this in ('0'..'9')
}
private fun Char.isHexNumber(): Boolean {
return this.isNumber()
|| this in ('a'..'f')
|| this in ('A'..'F')
}
} }

View File

@ -10,196 +10,200 @@ import io.neolang.ast.node.*
* @author kiva * @author kiva
*/ */
class NeoLangParser { class NeoLangParser {
private val lexer = NeoLangLexer() private val lexer = NeoLangLexer()
private var tokens = mutableListOf<NeoLangToken>() private var tokens = mutableListOf<NeoLangToken>()
private var currentPosition: Int = 0 private var currentPosition: Int = 0
private var currentToken: NeoLangToken? = null private var currentToken: NeoLangToken? = null
fun setInputSource(programCode: String?) { fun setInputSource(programCode: String?) {
lexer.setInputSource(programCode) lexer.setInputSource(programCode)
}
fun parse(): NeoLangAst {
return updateParserStatus(lexer.lex()) ?: throw ParseException("AST is null")
}
private fun updateParserStatus(tokens: List<NeoLangToken>): NeoLangAst? {
if (tokens.isEmpty()) {
// Allow empty program
return NeoLangProgramNode.emptyNode()
} }
fun parse(): NeoLangAst { this.tokens.clear()
return updateParserStatus(lexer.lex()) ?: throw ParseException("AST is null") this.tokens.addAll(tokens)
currentPosition = 0
currentToken = tokens[currentPosition]
return program()
}
private fun match(tokenType: NeoLangTokenType, errorThrow: Boolean = false): Boolean {
val currentToken = this.currentToken ?: throw InvalidTokenException("Unexpected token: null")
if (currentToken.tokenType === tokenType) {
currentPosition++
if (currentPosition >= tokens.size) {
this.currentToken = NeoLangToken(NeoLangTokenType.EOF, NeoLangTokenValue.EOF)
} else {
this.currentToken = tokens[currentPosition]
}
return true
} else if (errorThrow) {
throw InvalidTokenException(
"Unexpected token `${currentToken.tokenValue}' typed " +
"`${currentToken.tokenType}' near line ${currentToken.lineNumber}, " +
"expected $tokenType"
)
} }
private fun updateParserStatus(tokens: List<NeoLangToken>): NeoLangAst? { return false
if (tokens.isEmpty()) { }
// Allow empty program
return NeoLangProgramNode.emptyNode() private fun program(): NeoLangProgramNode {
val token = currentToken
var group = group()
if (group != null) {
val groups = mutableListOf(group)
while (token?.tokenType !== NeoLangTokenType.EOF) {
group = group()
if (group == null) {
break
} }
groups.add(group)
this.tokens.clear() }
this.tokens.addAll(tokens) return NeoLangProgramNode(groups)
currentPosition = 0
currentToken = tokens[currentPosition]
return program()
} }
private fun match(tokenType: NeoLangTokenType, errorThrow: Boolean = false): Boolean { return NeoLangProgramNode.emptyNode()
val currentToken = this.currentToken ?: throw InvalidTokenException("Unexpected token: null") }
if (currentToken.tokenType === tokenType) { /**
currentPosition++ * @param attrName Only available when group is a attribute value
if (currentPosition >= tokens.size) { */
this.currentToken = NeoLangToken(NeoLangTokenType.EOF, NeoLangTokenValue.EOF) private fun group(): NeoLangGroupNode? {
} else { val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
this.currentToken = tokens[currentPosition]
}
return true
} else if (errorThrow) { var attr = attribute()
throw InvalidTokenException("Unexpected token `${currentToken.tokenValue}' typed " + if (attr != null) {
"`${currentToken.tokenType}' near line ${currentToken.lineNumber}, " + val attributes = mutableListOf(attr)
"expected $tokenType")
while (token.tokenType !== NeoLangTokenType.EOF
&& token.tokenType !== NeoLangTokenType.BRACKET_END
&& token.tokenType !== NeoLangTokenType.ARRAY_END
) {
attr = attribute()
if (attr == null) {
break
} }
attributes.add(attr)
return false }
return NeoLangGroupNode(attributes.toTypedArray())
} }
private fun program(): NeoLangProgramNode { return null
val token = currentToken }
var group = group() private fun attribute(): NeoLangAttributeNode? {
if (group != null) { val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
val groups = mutableListOf(group) if (match(NeoLangTokenType.ID)) {
while (token?.tokenType !== NeoLangTokenType.EOF) { match(NeoLangTokenType.COLON, errorThrow = true)
group = group()
if (group == null) { val attrName = NeoLangStringNode(token)
break
} val block = block(attrName) ?: NeoLangBlockNode.emptyNode()
groups.add(group) return NeoLangAttributeNode(attrName, block)
} }
return NeoLangProgramNode(groups) return null
}
private fun array(arrayName: NeoLangStringNode): NeoLangArrayNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
// TODO: Multiple Array
var block = blockNonArrayElement(arrayName)
var index = 0
if (block != null) {
val elements = mutableListOf(NeoLangArrayNode.Companion.ArrayElement(index++, block))
if (match(NeoLangTokenType.COMMA)) {
// More than one elements
while (token.tokenType !== NeoLangTokenType.EOF
&& token.tokenType !== NeoLangTokenType.ARRAY_END
) {
block = blockNonArrayElement(arrayName)
if (block == null) {
break
}
elements.add(NeoLangArrayNode.Companion.ArrayElement(index++, block))
// Meet the last element
if (!match(NeoLangTokenType.COMMA)) {
break
}
} }
}
return NeoLangProgramNode.emptyNode() return NeoLangArrayNode(arrayName, elements.toTypedArray())
} }
/** return null
* @param attrName Only available when group is a attribute value }
*/
private fun group(): NeoLangGroupNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
var attr = attribute()
if (attr != null) {
val attributes = mutableListOf(attr)
while (token.tokenType !== NeoLangTokenType.EOF /**
&& token.tokenType !== NeoLangTokenType.BRACKET_END * @param attrName The block holder's name
&& token.tokenType !== NeoLangTokenType.ARRAY_END) { */
attr = attribute() private fun block(attrName: NeoLangStringNode): NeoLangBlockNode? {
if (attr == null) { val block = blockNonArrayElement(attrName)
break if (block != null) {
} return block
attributes.add(attr)
}
return NeoLangGroupNode(attributes.toTypedArray())
}
return null
} }
private fun attribute(): NeoLangAttributeNode? { val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null") when (token.tokenType) {
if (match(NeoLangTokenType.ID)) { NeoLangTokenType.ARRAY_START -> {
match(NeoLangTokenType.COLON, errorThrow = true) match(NeoLangTokenType.ARRAY_START, errorThrow = true)
val array = array(attrName)
match(NeoLangTokenType.ARRAY_END, errorThrow = true)
val attrName = NeoLangStringNode(token) // Allow empty arrays
return if (array != null) NeoLangBlockNode(array) else NeoLangBlockNode.emptyNode()
}
val block = block(attrName) ?: NeoLangBlockNode.emptyNode() else -> throw InvalidTokenException("Unexpected token `${token.tokenValue}' typed `${token.tokenType}' for block")
return NeoLangAttributeNode(attrName, block)
}
return null
} }
}
private fun array(arrayName: NeoLangStringNode): NeoLangArrayNode? { /**
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null") * @param attrName Only available when group is a attribute value
*/
private fun blockNonArrayElement(attrName: NeoLangStringNode?): NeoLangBlockNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
// TODO: Multiple Array return when (token.tokenType) {
var block = blockNonArrayElement(arrayName) NeoLangTokenType.NUMBER -> {
var index = 0 match(NeoLangTokenType.NUMBER, errorThrow = true)
return NeoLangBlockNode(NeoLangNumberNode(token))
}
NeoLangTokenType.ID -> {
match(NeoLangTokenType.ID, errorThrow = true)
return NeoLangBlockNode(NeoLangStringNode(token))
}
NeoLangTokenType.STRING -> {
match(NeoLangTokenType.STRING, errorThrow = true)
return NeoLangBlockNode(NeoLangStringNode(token))
}
NeoLangTokenType.BRACKET_START -> {
match(NeoLangTokenType.BRACKET_START, errorThrow = true)
val group = group()
match(NeoLangTokenType.BRACKET_END, errorThrow = true)
if (block != null) { // Allow empty blocks
val elements = mutableListOf(NeoLangArrayNode.Companion.ArrayElement(index++, block)) return if (group != null) NeoLangBlockNode(group) else NeoLangBlockNode.emptyNode()
}
if (match(NeoLangTokenType.COMMA)) { else -> null
// More than one elements
while (token.tokenType !== NeoLangTokenType.EOF
&& token.tokenType !== NeoLangTokenType.ARRAY_END) {
block = blockNonArrayElement(arrayName)
if (block == null) {
break
}
elements.add(NeoLangArrayNode.Companion.ArrayElement(index++, block))
// Meet the last element
if (!match(NeoLangTokenType.COMMA)) {
break
}
}
}
return NeoLangArrayNode(arrayName, elements.toTypedArray())
}
return null
}
/**
* @param attrName The block holder's name
*/
private fun block(attrName: NeoLangStringNode): NeoLangBlockNode? {
val block = blockNonArrayElement(attrName)
if (block != null) {
return block
}
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
when (token.tokenType) {
NeoLangTokenType.ARRAY_START -> {
match(NeoLangTokenType.ARRAY_START, errorThrow = true)
val array = array(attrName)
match(NeoLangTokenType.ARRAY_END, errorThrow = true)
// Allow empty arrays
return if (array != null) NeoLangBlockNode(array) else NeoLangBlockNode.emptyNode()
}
else -> throw InvalidTokenException("Unexpected token `${token.tokenValue}' typed `${token.tokenType}' for block")
}
}
/**
* @param attrName Only available when group is a attribute value
*/
private fun blockNonArrayElement(attrName: NeoLangStringNode?): NeoLangBlockNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
return when (token.tokenType) {
NeoLangTokenType.NUMBER -> {
match(NeoLangTokenType.NUMBER, errorThrow = true)
return NeoLangBlockNode(NeoLangNumberNode(token))
}
NeoLangTokenType.ID -> {
match(NeoLangTokenType.ID, errorThrow = true)
return NeoLangBlockNode(NeoLangStringNode(token))
}
NeoLangTokenType.STRING -> {
match(NeoLangTokenType.STRING, errorThrow = true)
return NeoLangBlockNode(NeoLangStringNode(token))
}
NeoLangTokenType.BRACKET_START -> {
match(NeoLangTokenType.BRACKET_START, errorThrow = true)
val group = group()
match(NeoLangTokenType.BRACKET_END, errorThrow = true)
// Allow empty blocks
return if (group != null) NeoLangBlockNode(group) else NeoLangBlockNode.emptyNode()
}
else -> null
}
} }
}
} }

View File

@ -6,34 +6,34 @@ import io.neolang.runtime.type.NeoLangValue
* @author kiva * @author kiva
*/ */
class NeoLangContext(val contextName: String) { class NeoLangContext(val contextName: String) {
companion object { companion object {
private val emptyContext = NeoLangContext("<Context-Empty>") private val emptyContext = NeoLangContext("<Context-Empty>")
} }
private val attributes = mutableMapOf<String, NeoLangValue>() private val attributes = mutableMapOf<String, NeoLangValue>()
val children = mutableListOf<NeoLangContext>() val children = mutableListOf<NeoLangContext>()
var parent: NeoLangContext? = null var parent: NeoLangContext? = null
fun defineAttribute(attributeName: String, attributeValue: NeoLangValue): NeoLangContext { fun defineAttribute(attributeName: String, attributeValue: NeoLangValue): NeoLangContext {
attributes[attributeName] = attributeValue attributes[attributeName] = attributeValue
return this return this
} }
fun getAttribute(attributeName: String): NeoLangValue { fun getAttribute(attributeName: String): NeoLangValue {
return attributes[attributeName] ?: parent?.getAttribute(attributeName) ?: NeoLangValue.UNDEFINED return attributes[attributeName] ?: parent?.getAttribute(attributeName) ?: NeoLangValue.UNDEFINED
} }
fun getChild(contextName: String): NeoLangContext { fun getChild(contextName: String): NeoLangContext {
var found: NeoLangContext? = null var found: NeoLangContext? = null
children.forEach { children.forEach {
if (it.contextName == contextName) { if (it.contextName == contextName) {
found = it found = it
} }
}
return found ?: emptyContext
} }
return found ?: emptyContext
}
fun getAttributes(): Map<String, NeoLangValue> { fun getAttributes(): Map<String, NeoLangValue> {
return attributes return attributes
} }
} }

View File

@ -5,55 +5,58 @@ import io.neolang.runtime.context.NeoLangContext
/** /**
* @author kiva * @author kiva
*/ */
class NeoLangArray private constructor(val elements: List<NeoLangArrayElement>, override val size: Int = elements.size) : Collection<NeoLangArrayElement> { class NeoLangArray private constructor(
companion object { val elements: List<NeoLangArrayElement>,
internal class PrimaryElement(val primaryValue: NeoLangValue) : NeoLangArrayElement() { override val size: Int = elements.size
override fun eval(): NeoLangValue { ) : Collection<NeoLangArrayElement> {
return primaryValue companion object {
} internal class PrimaryElement(val primaryValue: NeoLangValue) : NeoLangArrayElement() {
} override fun eval(): NeoLangValue {
return primaryValue
internal class BlockElement(val blockContext: NeoLangContext) : NeoLangArrayElement() { }
override fun eval(key: String): NeoLangValue {
return blockContext.getAttribute(key)
}
override fun isBlock(): Boolean {
return true
}
}
fun createFromContext(context: NeoLangContext): NeoLangArray {
val elements = mutableListOf<NeoLangArrayElement>()
context.getAttributes().entries.forEach {
val index = it.key.toInt()
elements.add(index, PrimaryElement(it.value))
}
context.children.forEach {
val index = it.contextName.toInt()
elements.add(index, BlockElement(it))
}
return NeoLangArray(elements)
}
} }
operator fun get(index: Int): NeoLangArrayElement { internal class BlockElement(val blockContext: NeoLangContext) : NeoLangArrayElement() {
return elements[index] override fun eval(key: String): NeoLangValue {
return blockContext.getAttribute(key)
}
override fun isBlock(): Boolean {
return true
}
} }
override fun contains(element: NeoLangArrayElement): Boolean { fun createFromContext(context: NeoLangContext): NeoLangArray {
return elements.contains(element) val elements = mutableListOf<NeoLangArrayElement>()
context.getAttributes().entries.forEach {
val index = it.key.toInt()
elements.add(index, PrimaryElement(it.value))
}
context.children.forEach {
val index = it.contextName.toInt()
elements.add(index, BlockElement(it))
}
return NeoLangArray(elements)
} }
}
override fun containsAll(elements: Collection<NeoLangArrayElement>): Boolean { operator fun get(index: Int): NeoLangArrayElement {
return this.elements.containsAll(elements) return elements[index]
} }
override fun isEmpty(): Boolean { override fun contains(element: NeoLangArrayElement): Boolean {
return size == 0 return elements.contains(element)
} }
override fun iterator(): Iterator<NeoLangArrayElement> { override fun containsAll(elements: Collection<NeoLangArrayElement>): Boolean {
return elements.iterator() return this.elements.containsAll(elements)
} }
override fun isEmpty(): Boolean {
return size == 0
}
override fun iterator(): Iterator<NeoLangArrayElement> {
return elements.iterator()
}
} }

View File

@ -4,15 +4,15 @@ package io.neolang.runtime.type
* @author kiva * @author kiva
*/ */
open class NeoLangArrayElement { open class NeoLangArrayElement {
open fun eval(): NeoLangValue { open fun eval(): NeoLangValue {
return NeoLangValue.UNDEFINED return NeoLangValue.UNDEFINED
} }
open fun eval(key: String): NeoLangValue { open fun eval(key: String): NeoLangValue {
return NeoLangValue.UNDEFINED return NeoLangValue.UNDEFINED
} }
open fun isBlock(): Boolean { open fun isBlock(): Boolean {
return false return false
} }
} }

View File

@ -4,27 +4,27 @@ package io.neolang.runtime.type
* @author kiva * @author kiva
*/ */
class NeoLangValue(private val rawValue: Any) { class NeoLangValue(private val rawValue: Any) {
fun asString(): String { fun asString(): String {
return rawValue.toString() return rawValue.toString()
}
fun asNumber(): Double {
if (rawValue is Array<*>) {
return 0.0
} }
fun asNumber(): Double { try {
if (rawValue is Array<*>) { return rawValue.toString().toDouble()
return 0.0 } catch (e: Throwable) {
} return 0.0
try {
return rawValue.toString().toDouble()
} catch (e: Throwable) {
return 0.0
}
} }
}
fun isValid(): Boolean { fun isValid(): Boolean {
return this != UNDEFINED return this != UNDEFINED
} }
companion object { companion object {
val UNDEFINED = NeoLangValue("<undefined>") val UNDEFINED = NeoLangValue("<undefined>")
} }
} }

View File

@ -6,68 +6,68 @@ import io.neolang.runtime.type.NeoLangArray
import io.neolang.runtime.type.NeoLangValue import io.neolang.runtime.type.NeoLangValue
class ConfigVisitor : IVisitorCallback { class ConfigVisitor : IVisitorCallback {
private var rootContext: NeoLangContext? = null private var rootContext: NeoLangContext? = null
private var currentContext: NeoLangContext? = null private var currentContext: NeoLangContext? = null
fun getRootContext(): NeoLangContext { fun getRootContext(): NeoLangContext {
return rootContext!! return rootContext!!
} }
fun getContext(contextPath: Array<String>): NeoLangContext { fun getContext(contextPath: Array<String>): NeoLangContext {
var context = getCurrentContext() var context = getCurrentContext()
contextPath.forEach { contextPath.forEach {
context = context.getChild(it) context = context.getChild(it)
}
return context
} }
return context
}
fun getAttribute(contextPath: Array<String>, attrName: String): NeoLangValue { fun getAttribute(contextPath: Array<String>, attrName: String): NeoLangValue {
return getContext(contextPath).getAttribute(attrName) return getContext(contextPath).getAttribute(attrName)
} }
fun getArray(contextPath: Array<String>, arrayName: String): NeoLangArray { fun getArray(contextPath: Array<String>, arrayName: String): NeoLangArray {
// We use NeoLangContext as arrays and array elements now // We use NeoLangContext as arrays and array elements now
return NeoLangArray.createFromContext(getContext(contextPath).getChild(arrayName)) return NeoLangArray.createFromContext(getContext(contextPath).getChild(arrayName))
} }
fun getStringValue(path: Array<String>, name: String): String? { fun getStringValue(path: Array<String>, name: String): String? {
val value = this.getAttribute(path, name) val value = this.getAttribute(path, name)
return if (value.isValid()) value.asString() else null return if (value.isValid()) value.asString() else null
} }
fun getBooleanValue(path: Array<String>, name: String): Boolean? { fun getBooleanValue(path: Array<String>, name: String): Boolean? {
val value = this.getAttribute(path, name) val value = this.getAttribute(path, name)
return if (value.isValid()) value.asString() == "true" else null return if (value.isValid()) value.asString() == "true" else null
} }
override fun onStart() { override fun onStart() {
currentContext = NeoLangContext("global") currentContext = NeoLangContext("global")
rootContext = currentContext rootContext = currentContext
} }
override fun onFinish() { override fun onFinish() {
var context = currentContext var context = currentContext
while (context != null && context.parent != null) { while (context != null && context.parent != null) {
context = context.parent context = context.parent
}
this.currentContext = context
} }
this.currentContext = context
}
override fun onEnterContext(contextName: String) { override fun onEnterContext(contextName: String) {
val newContext = NeoLangContext(contextName) val newContext = NeoLangContext(contextName)
newContext.parent = currentContext newContext.parent = currentContext
currentContext!!.children.add(newContext) currentContext!!.children.add(newContext)
currentContext = newContext currentContext = newContext
} }
override fun onExitContext() { override fun onExitContext() {
val context = currentContext val context = currentContext
if (context?.parent != null) { if (context?.parent != null) {
this.currentContext = context.parent this.currentContext = context.parent
}
} }
}
override fun getCurrentContext(): NeoLangContext { override fun getCurrentContext(): NeoLangContext {
return currentContext!! return currentContext!!
} }
} }

View File

@ -8,35 +8,35 @@ import java.util.*
* @author kiva * @author kiva
*/ */
class DisplayProcessVisitor : IVisitorCallbackAdapter() { class DisplayProcessVisitor : IVisitorCallbackAdapter() {
private val contextStack = Stack<NeoLangContext>() private val contextStack = Stack<NeoLangContext>()
override fun onStart() { override fun onStart() {
println(">>> Start") println(">>> Start")
onEnterContext("global") onEnterContext("global")
} }
override fun onFinish() { override fun onFinish() {
while (contextStack.isNotEmpty()) { while (contextStack.isNotEmpty()) {
onExitContext() onExitContext()
}
println(">>> Finish")
} }
println(">>> Finish")
}
override fun onEnterContext(contextName: String) { override fun onEnterContext(contextName: String) {
val context = NeoLangContext(contextName) val context = NeoLangContext(contextName)
contextStack.push(context) contextStack.push(context)
println(">>> Entering Context `$contextName'") println(">>> Entering Context `$contextName'")
} }
override fun onExitContext() { override fun onExitContext() {
val context = contextStack.pop() val context = contextStack.pop()
println(">>> Exiting & Dumping Context ${context.contextName}") println(">>> Exiting & Dumping Context ${context.contextName}")
context.getAttributes().entries.forEach { context.getAttributes().entries.forEach {
println(" > [${it.key}]: ${it.value.asString()}") println(" > [${it.key}]: ${it.value.asString()}")
}
} }
}
override fun getCurrentContext(): NeoLangContext { override fun getCurrentContext(): NeoLangContext {
return contextStack.peek() return contextStack.peek()
} }
} }

View File

@ -9,9 +9,9 @@ import org.junit.Test
* @see [Testing documentation](http://d.android.com/tools/testing) * @see [Testing documentation](http://d.android.com/tools/testing)
*/ */
class NeoLangTest { class NeoLangTest {
@Test @Test
fun arrayTest() { fun arrayTest() {
Main.main(arrayOf("NeoLang/example/extra-key.nl")) Main.main(arrayOf("NeoLang/example/extra-key.nl"))
} }
} }

View File

@ -4,28 +4,28 @@ def libraryVersionCode = 1
def libraryVersionName = "1.0" def libraryVersionName = "1.0"
android { android {
compileSdkVersion rootProject.ext.android.COMPILE_SDK_VERSION compileSdkVersion rootProject.ext.android.COMPILE_SDK_VERSION
defaultConfig { defaultConfig {
minSdkVersion rootProject.ext.android.MIN_SDK_VERSION minSdkVersion rootProject.ext.android.MIN_SDK_VERSION
targetSdkVersion rootProject.ext.android.TARGET_SDK_VERSION targetSdkVersion rootProject.ext.android.TARGET_SDK_VERSION
versionCode libraryVersionCode versionCode libraryVersionCode
versionName libraryVersionName versionName libraryVersionName
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
}
} }
}
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
}) })
testImplementation rootProject.ext.deps["junit"] testImplementation rootProject.ext.deps["junit"]
} }

View File

@ -1,2 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest
package="io.neoterm.bridge" /> package="io.neoterm.bridge"/>

View File

@ -9,52 +9,52 @@ import java.util.Objects;
* @author kiva * @author kiva
*/ */
public class Bridge { public class Bridge {
public static final String ACTION_EXECUTE = "neoterm.action.remote.execute"; public static final String ACTION_EXECUTE = "neoterm.action.remote.execute";
public static final String ACTION_SILENT_RUN = "neoterm.action.remote.silent-run"; public static final String ACTION_SILENT_RUN = "neoterm.action.remote.silent-run";
public static final String EXTRA_COMMAND = "neoterm.extra.remote.execute.command"; public static final String EXTRA_COMMAND = "neoterm.extra.remote.execute.command";
public static final String EXTRA_SESSION_ID = "neoterm.extra.remote.execute.session"; public static final String EXTRA_SESSION_ID = "neoterm.extra.remote.execute.session";
public static final String EXTRA_FOREGROUND = "neoterm.extra.remote.execute.foreground"; public static final String EXTRA_FOREGROUND = "neoterm.extra.remote.execute.foreground";
private static final String NEOTERM_PACKAGE = "io.neoterm"; private static final String NEOTERM_PACKAGE = "io.neoterm";
private static final String NEOTERM_REMOTE_INTERFACE = "io.neoterm.ui.term.NeoTermRemoteInterface"; private static final String NEOTERM_REMOTE_INTERFACE = "io.neoterm.ui.term.NeoTermRemoteInterface";
private static final ComponentName NEOTERM_COMPONENT = new ComponentName(NEOTERM_PACKAGE, NEOTERM_REMOTE_INTERFACE); private static final ComponentName NEOTERM_COMPONENT = new ComponentName(NEOTERM_PACKAGE, NEOTERM_REMOTE_INTERFACE);
private Bridge() throws IllegalAccessException { private Bridge() throws IllegalAccessException {
throw new IllegalAccessException(); throw new IllegalAccessException();
} }
public static Intent createExecuteIntent(SessionId sessionId, public static Intent createExecuteIntent(SessionId sessionId,
String command, String command,
boolean foreground) { boolean foreground) {
Objects.requireNonNull(command, "command"); Objects.requireNonNull(command, "command");
Objects.requireNonNull(sessionId, "session id"); Objects.requireNonNull(sessionId, "session id");
Intent intent = new Intent(ACTION_EXECUTE); Intent intent = new Intent(ACTION_EXECUTE);
intent.setComponent(NEOTERM_COMPONENT); intent.setComponent(NEOTERM_COMPONENT);
intent.putExtra(EXTRA_COMMAND, command); intent.putExtra(EXTRA_COMMAND, command);
intent.putExtra(EXTRA_SESSION_ID, sessionId.getSessionId()); intent.putExtra(EXTRA_SESSION_ID, sessionId.getSessionId());
intent.putExtra(EXTRA_FOREGROUND, foreground); intent.putExtra(EXTRA_FOREGROUND, foreground);
return intent; return intent;
} }
public static Intent createExecuteIntent(SessionId sessionId, String command) { public static Intent createExecuteIntent(SessionId sessionId, String command) {
return createExecuteIntent(sessionId, command, true); return createExecuteIntent(sessionId, command, true);
} }
public static Intent createExecuteIntent(String command) { public static Intent createExecuteIntent(String command) {
return createExecuteIntent(SessionId.NEW_SESSION, command); return createExecuteIntent(SessionId.NEW_SESSION, command);
} }
public static Intent createExecuteIntent(String command, boolean foreground) { public static Intent createExecuteIntent(String command, boolean foreground) {
return createExecuteIntent(SessionId.NEW_SESSION, command, foreground); return createExecuteIntent(SessionId.NEW_SESSION, command, foreground);
} }
public static SessionId parseResult(Intent data) { public static SessionId parseResult(Intent data) {
Objects.requireNonNull(data, "data"); Objects.requireNonNull(data, "data");
if (data.hasExtra(EXTRA_SESSION_ID)) { if (data.hasExtra(EXTRA_SESSION_ID)) {
String handle = data.getStringExtra(EXTRA_SESSION_ID); String handle = data.getStringExtra(EXTRA_SESSION_ID);
return SessionId.of(handle); return SessionId.of(handle);
}
return null;
} }
return null;
}
} }

View File

@ -6,45 +6,45 @@ import java.util.Objects;
* @author kiva * @author kiva
*/ */
public class SessionId { public class SessionId {
/** /**
* Created a new session. * Created a new session.
*/ */
public static final SessionId NEW_SESSION = SessionId.of("new"); public static final SessionId NEW_SESSION = SessionId.of("new");
/** /**
* Presents current session in NeoTerm. * Presents current session in NeoTerm.
*/ */
public static final SessionId CURRENT_SESSION = SessionId.of("current"); public static final SessionId CURRENT_SESSION = SessionId.of("current");
private final String sessionId; private final String sessionId;
SessionId(String sessionId) { SessionId(String sessionId) {
this.sessionId = sessionId; this.sessionId = sessionId;
} }
public String getSessionId() { public String getSessionId() {
return sessionId; return sessionId;
} }
@Override @Override
public String toString() { public String toString() {
return "TerminalSession { id = " + sessionId + " }"; return "TerminalSession { id = " + sessionId + " }";
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
SessionId sessionId1 = (SessionId) o; SessionId sessionId1 = (SessionId) o;
return Objects.equals(sessionId, sessionId1.sessionId); return Objects.equals(sessionId, sessionId1.sessionId);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(sessionId); return Objects.hash(sessionId);
} }
public static SessionId of(String sessionId) { public static SessionId of(String sessionId) {
return new SessionId(sessionId); return new SessionId(sessionId);
} }
} }

View File

@ -1,3 +1,3 @@
<resources> <resources>
<string name="app_name">NeoTermBridge</string> <string name="app_name">NeoTermBridge</string>
</resources> </resources>

View File

@ -2,7 +2,7 @@ package io.neoterm.bridge;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
/** /**
* Example local unit test, which will execute on the development machine (host). * Example local unit test, which will execute on the development machine (host).
@ -10,8 +10,8 @@ import static org.junit.Assert.*;
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/ */
public class ExampleUnitTest { public class ExampleUnitTest {
@Test @Test
public void addition_isCorrect() { public void addition_isCorrect() {
assertEquals(4, 2 + 2); assertEquals(4, 2 + 2);
} }
} }

View File

@ -7,14 +7,18 @@ NeoTerm
A modern-designed android terminal emulator for the 21st century. A modern-designed android terminal emulator for the 21st century.
### Our Pledge ### Our Pledge
Originally, NeoTerm was designed as the front end of Termux to provide some functions that Termux didn't have, but we found it very convenient. In continuous development, we discovered our goal: to be the best terminal for Android.
Originally, NeoTerm was designed as the front end of Termux to provide some functions that Termux didn't have, but we
found it very convenient. In continuous development, we discovered our goal: to be the best terminal for Android.
### Help & Documentation ### Help & Documentation
View on [GitBook](https://neoterm.gitbooks.io/neoterm-wiki/content) View on [GitBook](https://neoterm.gitbooks.io/neoterm-wiki/content)
View on [GitHub](https://github.com/NeoTerm/NeoTerm-Wiki) View on [GitHub](https://github.com/NeoTerm/NeoTerm-Wiki)
### Download ### Download
[GitHub Release Page](https://github.com/NeoTerm/NeoTerm/releases) [GitHub Release Page](https://github.com/NeoTerm/NeoTerm/releases)
[lzzySoft's F-Droid repo](https://apt.izzysoft.de/fdroid/index/apk/io.neoterm) (thanks to @lzzySoft) [lzzySoft's F-Droid repo](https://apt.izzysoft.de/fdroid/index/apk/io.neoterm) (thanks to @lzzySoft)

View File

@ -1,36 +1,36 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
compileSdkVersion rootProject.ext.android.COMPILE_SDK_VERSION compileSdkVersion rootProject.ext.android.COMPILE_SDK_VERSION
defaultConfig { defaultConfig {
minSdkVersion rootProject.ext.android.MIN_SDK_VERSION minSdkVersion rootProject.ext.android.MIN_SDK_VERSION
targetSdkVersion rootProject.ext.android.TARGET_SDK_VERSION targetSdkVersion rootProject.ext.android.TARGET_SDK_VERSION
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
}
} }
}
sourceSets { sourceSets {
main { main {
jniLibs.srcDirs = ['src/main/jniLibs'] jniLibs.srcDirs = ['src/main/jniLibs']
}
} }
}
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation rootProject.ext.deps["appcompat-v7"] implementation rootProject.ext.deps["appcompat-v7"]
testImplementation rootProject.ext.deps["junit"] testImplementation rootProject.ext.deps["junit"]
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
}) })
} }

View File

@ -1,2 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest
package="io.neoterm.xorg" /> package="io.neoterm.xorg"/>

View File

@ -27,304 +27,269 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent; import android.hardware.SensorEvent;
import android.hardware.SensorEventListener; import android.hardware.SensorEventListener;
import android.hardware.SensorManager; import android.hardware.SensorManager;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log; import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Arrays; import java.util.Arrays;
@SuppressWarnings("JniMissingFunction") @SuppressWarnings("JniMissingFunction")
class AccelerometerReader implements SensorEventListener class AccelerometerReader implements SensorEventListener {
{
private SensorManager _manager = null; private SensorManager _manager = null;
public boolean openedBySDL = false; public boolean openedBySDL = false;
public static final GyroscopeListener gyro = new GyroscopeListener(); public static final GyroscopeListener gyro = new GyroscopeListener();
public static final OrientationListener orientation = new OrientationListener(); public static final OrientationListener orientation = new OrientationListener();
public AccelerometerReader(Context context) public AccelerometerReader(Context context) {
{ _manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
_manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); }
}
public synchronized void stop()
{
if( _manager != null )
{
Log.i("SDL", "libSDL: stopping accelerometer/gyroscope/orientation");
_manager.unregisterListener(this);
_manager.unregisterListener(gyro);
_manager.unregisterListener(orientation);
}
}
public synchronized void start() public synchronized void stop() {
{ if (_manager != null) {
if( (Globals.UseAccelerometerAsArrowKeys || Globals.AppUsesAccelerometer) && Log.i("SDL", "libSDL: stopping accelerometer/gyroscope/orientation");
_manager != null && _manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null ) _manager.unregisterListener(this);
{ _manager.unregisterListener(gyro);
Log.i("SDL", "libSDL: starting accelerometer"); _manager.unregisterListener(orientation);
_manager.registerListener(this, _manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); }
} }
if( (Globals.AppUsesGyroscope || Globals.MoveMouseWithGyroscope) &&
_manager != null && _manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null )
{
Log.i("SDL", "libSDL: starting gyroscope");
_manager.registerListener(gyro, _manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_GAME);
}
if( (Globals.AppUsesOrientationSensor) && _manager != null &&
_manager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR) != null )
{
Log.i("SDL", "libSDL: starting orientation sensor");
_manager.registerListener(orientation, _manager.getDefaultSensor(
Sensor.TYPE_GAME_ROTATION_VECTOR),
SensorManager.SENSOR_DELAY_GAME);
}
}
public void onSensorChanged(SensorEvent event) public synchronized void start() {
{ if ((Globals.UseAccelerometerAsArrowKeys || Globals.AppUsesAccelerometer) &&
if( Globals.HorizontalOrientation ) _manager != null && _manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null) {
{ Log.i("SDL", "libSDL: starting accelerometer");
if( gyro.invertedOrientation ) _manager.registerListener(this, _manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME);
nativeAccelerometer(-event.values[1], event.values[0], event.values[2]); }
else if ((Globals.AppUsesGyroscope || Globals.MoveMouseWithGyroscope) &&
nativeAccelerometer(event.values[1], -event.values[0], event.values[2]); _manager != null && _manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null) {
} Log.i("SDL", "libSDL: starting gyroscope");
else _manager.registerListener(gyro, _manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_GAME);
nativeAccelerometer(event.values[0], event.values[1], event.values[2]); // TODO: not tested! }
} if ((Globals.AppUsesOrientationSensor) && _manager != null &&
_manager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR) != null) {
Log.i("SDL", "libSDL: starting orientation sensor");
_manager.registerListener(orientation, _manager.getDefaultSensor(
Sensor.TYPE_GAME_ROTATION_VECTOR),
SensorManager.SENSOR_DELAY_GAME);
}
}
public void onAccuracyChanged(Sensor s, int a) public void onSensorChanged(SensorEvent event) {
{ if (Globals.HorizontalOrientation) {
} if (gyro.invertedOrientation)
nativeAccelerometer(-event.values[1], event.values[0], event.values[2]);
else
nativeAccelerometer(event.values[1], -event.values[0], event.values[2]);
} else
nativeAccelerometer(event.values[0], event.values[1], event.values[2]); // TODO: not tested!
}
static class GyroscopeListener implements SensorEventListener public void onAccuracyChanged(Sensor s, int a) {
{ }
public boolean invertedOrientation = false;
// Noise filter with sane initial values, so user will be able static class GyroscopeListener implements SensorEventListener {
// to move gyroscope during the first 10 seconds, while the noise is measured. public boolean invertedOrientation = false;
// After that the values are replaced by noiseMin/noiseMax.
final float filterMin[] = new float[] { -0.05f, -0.05f, -0.05f };
final float filterMax[] = new float[] { 0.05f, 0.05f, 0.05f };
// The noise levels we're measuring. // Noise filter with sane initial values, so user will be able
// Large initial values, they will decrease, but never increase. // to move gyroscope during the first 10 seconds, while the noise is measured.
float noiseMin[] = new float[] { -1.0f, -1.0f, -1.0f }; // After that the values are replaced by noiseMin/noiseMax.
float noiseMax[] = new float[] { 1.0f, 1.0f, 1.0f }; final float filterMin[] = new float[]{-0.05f, -0.05f, -0.05f};
final float filterMax[] = new float[]{0.05f, 0.05f, 0.05f};
// The gyro data buffer, from which we care calculating min/max noise values. // The noise levels we're measuring.
// The bigger it is, the more precise the calclations, and the longer it takes to converge. // Large initial values, they will decrease, but never increase.
float noiseData[][] = new float[200][noiseMin.length]; float noiseMin[] = new float[]{-1.0f, -1.0f, -1.0f};
int noiseDataIdx = 0; float noiseMax[] = new float[]{1.0f, 1.0f, 1.0f};
// When we detect movement, we remove last few values of the measured data. // The gyro data buffer, from which we care calculating min/max noise values.
// The movement is detected by comparing values to noiseMin/noiseMax of the previous iteration. // The bigger it is, the more precise the calclations, and the longer it takes to converge.
int movementBackoff = 0; float noiseData[][] = new float[200][noiseMin.length];
int noiseDataIdx = 0;
// Difference between min/max in the previous measurement iteration, // When we detect movement, we remove last few values of the measured data.
// used to determine when we should stop measuring, when the change becomes negligilbe. // The movement is detected by comparing values to noiseMin/noiseMax of the previous iteration.
float measuredNoiseRange[] = null; int movementBackoff = 0;
// How long the algorithm is running, to stop it if it does not converge. // Difference between min/max in the previous measurement iteration,
int measurementIteration = 0; // used to determine when we should stop measuring, when the change becomes negligilbe.
float measuredNoiseRange[] = null;
public GyroscopeListener() // How long the algorithm is running, to stop it if it does not converge.
{ int measurementIteration = 0;
}
void collectNoiseData(final float[] data) public GyroscopeListener() {
{ }
for( int i = 0; i < noiseMin.length; i++ )
{
if( data[i] < noiseMin[i] || data[i] > noiseMax[i] )
{
// Movement detected, this can converge our min/max too early, so we're discarding last few values
if( movementBackoff < 0 )
{
int discard = 10;
if( -movementBackoff < discard )
discard = -movementBackoff;
noiseDataIdx -= discard;
if( noiseDataIdx < 0 )
noiseDataIdx = 0;
}
movementBackoff = 10;
return;
}
noiseData[noiseDataIdx][i] = data[i];
}
movementBackoff--;
if( movementBackoff >= 0 )
return; // Also discard several values after the movement stopped
noiseDataIdx++;
if( noiseDataIdx < noiseData.length ) void collectNoiseData(final float[] data) {
return; for (int i = 0; i < noiseMin.length; i++) {
if (data[i] < noiseMin[i] || data[i] > noiseMax[i]) {
// Movement detected, this can converge our min/max too early, so we're discarding last few values
if (movementBackoff < 0) {
int discard = 10;
if (-movementBackoff < discard)
discard = -movementBackoff;
noiseDataIdx -= discard;
if (noiseDataIdx < 0)
noiseDataIdx = 0;
}
movementBackoff = 10;
return;
}
noiseData[noiseDataIdx][i] = data[i];
}
movementBackoff--;
if (movementBackoff >= 0)
return; // Also discard several values after the movement stopped
noiseDataIdx++;
measurementIteration++; if (noiseDataIdx < noiseData.length)
Log.d( "SDL", "GYRO_NOISE: Measuring in progress... " + measurementIteration ); return;
if( measurementIteration > 5 )
{
// We've collected enough data to use our noise min/max values as a new filter
System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length);
System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length);
}
if( measurementIteration > 15 )
{
Log.d( "SDL", "GYRO_NOISE: Measuring done! Maximum number of iterations reached: " + measurementIteration );
noiseData = null;
measuredNoiseRange = null;
return;
}
noiseDataIdx = 0; measurementIteration++;
boolean changed = false; Log.d("SDL", "GYRO_NOISE: Measuring in progress... " + measurementIteration);
for( int i = 0; i < noiseMin.length; i++ ) if (measurementIteration > 5) {
{ // We've collected enough data to use our noise min/max values as a new filter
float min = 1.0f; System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length);
float max = -1.0f; System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length);
for( int ii = 0; ii < noiseData.length; ii++ ) }
{ if (measurementIteration > 15) {
if( min > noiseData[ii][i] ) Log.d("SDL", "GYRO_NOISE: Measuring done! Maximum number of iterations reached: " + measurementIteration);
min = noiseData[ii][i]; noiseData = null;
if( max < noiseData[ii][i] ) measuredNoiseRange = null;
max = noiseData[ii][i]; return;
} }
// Increase the range a bit, for safe conservative filtering
float middle = (min + max) / 2.0f;
min += (min - middle) * 0.2f;
max += (max - middle) * 0.2f;
// Check if range between min/max is less then the current range, as a safety measure,
// and min/max range is not jumping outside of previously measured range
if( max - min < noiseMax[i] - noiseMin[i] && min >= noiseMin[i] && max <= noiseMax[i] )
{
// Move old min/max closer to the measured min/max, but do not replace the values altogether
noiseMin[i] = (noiseMin[i] + min * 4.0f) / 5.0f;
noiseMax[i] = (noiseMax[i] + max * 4.0f) / 5.0f;
changed = true;
}
}
Log.d( "SDL", "GYRO_NOISE: MIN MAX: " + Arrays.toString(noiseMin) + " " + Arrays.toString(noiseMax) ); noiseDataIdx = 0;
boolean changed = false;
for (int i = 0; i < noiseMin.length; i++) {
float min = 1.0f;
float max = -1.0f;
for (int ii = 0; ii < noiseData.length; ii++) {
if (min > noiseData[ii][i])
min = noiseData[ii][i];
if (max < noiseData[ii][i])
max = noiseData[ii][i];
}
// Increase the range a bit, for safe conservative filtering
float middle = (min + max) / 2.0f;
min += (min - middle) * 0.2f;
max += (max - middle) * 0.2f;
// Check if range between min/max is less then the current range, as a safety measure,
// and min/max range is not jumping outside of previously measured range
if (max - min < noiseMax[i] - noiseMin[i] && min >= noiseMin[i] && max <= noiseMax[i]) {
// Move old min/max closer to the measured min/max, but do not replace the values altogether
noiseMin[i] = (noiseMin[i] + min * 4.0f) / 5.0f;
noiseMax[i] = (noiseMax[i] + max * 4.0f) / 5.0f;
changed = true;
}
}
if( !changed ) Log.d("SDL", "GYRO_NOISE: MIN MAX: " + Arrays.toString(noiseMin) + " " + Arrays.toString(noiseMax));
return;
// Determine when to stop measuring - check that the previous min/max range is close enough to the current one if (!changed)
return;
float range[] = new float[noiseMin.length]; // Determine when to stop measuring - check that the previous min/max range is close enough to the current one
for( int i = 0; i < noiseMin.length; i++ )
range[i] = noiseMax[i] - noiseMin[i];
Log.d( "SDL", "GYRO_NOISE: RANGE: " + Arrays.toString(range) + " " + Arrays.toString(measuredNoiseRange) ); float range[] = new float[noiseMin.length];
for (int i = 0; i < noiseMin.length; i++)
range[i] = noiseMax[i] - noiseMin[i];
if( measuredNoiseRange == null ) Log.d("SDL", "GYRO_NOISE: RANGE: " + Arrays.toString(range) + " " + Arrays.toString(measuredNoiseRange));
{
measuredNoiseRange = range;
return; // First iteration, skip further checks
}
for( int i = 0; i < range.length; i++ ) if (measuredNoiseRange == null) {
{ measuredNoiseRange = range;
if( measuredNoiseRange[i] / range[i] > 1.2f ) return; // First iteration, skip further checks
{ }
measuredNoiseRange = range;
return;
}
}
// We converged to the final min/max filter values, stop measuring for (int i = 0; i < range.length; i++) {
System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length); if (measuredNoiseRange[i] / range[i] > 1.2f) {
System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length); measuredNoiseRange = range;
noiseData = null; return;
measuredNoiseRange = null; }
Log.d( "SDL", "GYRO_NOISE: Measuring done! Range converged on iteration " + measurementIteration ); }
}
public void onSensorChanged(final SensorEvent event) // We converged to the final min/max filter values, stop measuring
{ System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length);
boolean filtered = true; System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length);
final float[] data = event.values; noiseData = null;
measuredNoiseRange = null;
Log.d("SDL", "GYRO_NOISE: Measuring done! Range converged on iteration " + measurementIteration);
}
if( noiseData != null ) public void onSensorChanged(final SensorEvent event) {
collectNoiseData(data); boolean filtered = true;
final float[] data = event.values;
for( int i = 0; i < 3; i++ ) if (noiseData != null)
{ collectNoiseData(data);
if( data[i] < filterMin[i] )
{
filtered = false;
data[i] -= filterMin[i];
}
else if( data[i] > filterMax[i] )
{
filtered = false;
data[i] -= filterMax[i];
}
}
if( filtered ) for (int i = 0; i < 3; i++) {
return; if (data[i] < filterMin[i]) {
filtered = false;
data[i] -= filterMin[i];
} else if (data[i] > filterMax[i]) {
filtered = false;
data[i] -= filterMax[i];
}
}
if( Globals.HorizontalOrientation ) if (filtered)
{ return;
if( invertedOrientation )
nativeGyroscope(-data[0], -data[1], data[2]);
else
nativeGyroscope(data[0], data[1], data[2]);
}
else
{
if( invertedOrientation )
nativeGyroscope(-data[1], data[0], data[2]);
else
nativeGyroscope(data[1], -data[0], data[2]);
}
}
public void onAccuracyChanged(Sensor s, int a) if (Globals.HorizontalOrientation) {
{ if (invertedOrientation)
} nativeGyroscope(-data[0], -data[1], data[2]);
public boolean available(AppCompatActivity context) else
{ nativeGyroscope(data[0], data[1], data[2]);
SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); } else {
return ( manager != null && manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null ); if (invertedOrientation)
} nativeGyroscope(-data[1], data[0], data[2]);
public void registerListener(AppCompatActivity context, SensorEventListener l) else
{ nativeGyroscope(data[1], -data[0], data[2]);
SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); }
if ( manager == null && manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) == null ) }
return;
manager.registerListener(gyro, manager.getDefaultSensor(
Globals.AppUsesOrientationSensor ? Sensor.TYPE_GAME_ROTATION_VECTOR : Sensor.TYPE_GYROSCOPE),
SensorManager.SENSOR_DELAY_GAME);
}
public void unregisterListener(AppCompatActivity context, SensorEventListener l)
{
SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
if ( manager == null )
return;
manager.unregisterListener(l);
}
}
static class OrientationListener implements SensorEventListener public void onAccuracyChanged(Sensor s, int a) {
{ }
public OrientationListener()
{
}
public void onSensorChanged(SensorEvent event)
{
nativeOrientation(event.values[0], event.values[1], event.values[2]);
}
public void onAccuracyChanged(Sensor s, int a)
{
}
}
private static native void nativeAccelerometer(float accX, float accY, float accZ); public boolean available(AppCompatActivity context) {
private static native void nativeGyroscope(float X, float Y, float Z); SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
private static native void nativeOrientation(float X, float Y, float Z); return (manager != null && manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null);
}
public void registerListener(AppCompatActivity context, SensorEventListener l) {
SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
if (manager == null && manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) == null)
return;
manager.registerListener(gyro, manager.getDefaultSensor(
Globals.AppUsesOrientationSensor ? Sensor.TYPE_GAME_ROTATION_VECTOR : Sensor.TYPE_GYROSCOPE),
SensorManager.SENSOR_DELAY_GAME);
}
public void unregisterListener(AppCompatActivity context, SensorEventListener l) {
SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
if (manager == null)
return;
manager.unregisterListener(l);
}
}
static class OrientationListener implements SensorEventListener {
public OrientationListener() {
}
public void onSensorChanged(SensorEvent event) {
nativeOrientation(event.values[0], event.values[1], event.values[2]);
}
public void onAccuracyChanged(Sensor s, int a) {
}
}
private static native void nativeAccelerometer(float accX, float accY, float accZ);
private static native void nativeGyroscope(float X, float Y, float Z);
private static native void nativeOrientation(float X, float Y, float Z);
} }

View File

@ -23,300 +23,256 @@ freely, subject to the following restrictions:
package io.neoterm; package io.neoterm;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.KeyEvent;
import android.view.Window;
import android.view.WindowManager;
import android.media.AudioTrack;
import android.media.AudioManager;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord; import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder.AudioSource; import android.media.MediaRecorder.AudioSource;
import java.io.*;
import android.util.Log; import android.util.Log;
import java.util.concurrent.Semaphore;
import android.Manifest;
import android.content.pm.PackageManager;
import io.neoterm.xorg.NeoXorgViewClient; import io.neoterm.xorg.NeoXorgViewClient;
import java.util.concurrent.Semaphore;
@SuppressWarnings("JniMissingFunction") @SuppressWarnings("JniMissingFunction")
class AudioThread class AudioThread {
{ private NeoXorgViewClient mClient;
private NeoXorgViewClient mClient; private AudioTrack mAudio;
private AudioTrack mAudio; private byte[] mAudioBuffer;
private byte[] mAudioBuffer; private int mVirtualBufSize;
private int mVirtualBufSize;
public AudioThread(NeoXorgViewClient client) public AudioThread(NeoXorgViewClient client) {
{ this.mClient = client;
this.mClient = client; mAudio = null;
mAudio = null; mAudioBuffer = null;
mAudioBuffer = null; nativeAudioInitJavaCallbacks();
nativeAudioInitJavaCallbacks(); }
}
public int fillBuffer()
{
if( mClient.isPaused() )
{
try{
Thread.sleep(500);
} catch (InterruptedException e) {}
}
else
{
//if( Globals.AudioBufferConfig == 0 ) // Gives too much spam to logcat, makes things worse
// mAudio.flush();
mAudio.write( mAudioBuffer, 0, mVirtualBufSize ); public int fillBuffer() {
} if (mClient.isPaused()) {
try {
return 1; Thread.sleep(500);
} } catch (InterruptedException e) {
}
public int initAudio(int rate, int channels, int encoding, int bufSize) } else {
{ //if( Globals.AudioBufferConfig == 0 ) // Gives too much spam to logcat, makes things worse
if( mAudio == null ) // mAudio.flush();
{
channels = ( channels == 1 ) ? AudioFormat.CHANNEL_CONFIGURATION_MONO :
AudioFormat.CHANNEL_CONFIGURATION_STEREO;
encoding = ( encoding == 1 ) ? AudioFormat.ENCODING_PCM_16BIT :
AudioFormat.ENCODING_PCM_8BIT;
mVirtualBufSize = bufSize; mAudio.write(mAudioBuffer, 0, mVirtualBufSize);
}
if( AudioTrack.getMinBufferSize( rate, channels, encoding ) > bufSize ) return 1;
bufSize = AudioTrack.getMinBufferSize( rate, channels, encoding ); }
if(Globals.AudioBufferConfig != 0) { // application's choice - use minimal buffer public int initAudio(int rate, int channels, int encoding, int bufSize) {
bufSize = (int)((float)bufSize * (((float)(Globals.AudioBufferConfig - 1) * 2.5f) + 1.0f)); if (mAudio == null) {
mVirtualBufSize = bufSize; channels = (channels == 1) ? AudioFormat.CHANNEL_CONFIGURATION_MONO :
} AudioFormat.CHANNEL_CONFIGURATION_STEREO;
mAudioBuffer = new byte[bufSize]; encoding = (encoding == 1) ? AudioFormat.ENCODING_PCM_16BIT :
AudioFormat.ENCODING_PCM_8BIT;
mAudio = new AudioTrack(AudioManager.STREAM_MUSIC, mVirtualBufSize = bufSize;
rate,
channels,
encoding,
bufSize,
AudioTrack.MODE_STREAM );
mAudio.play();
}
return mVirtualBufSize;
}
public byte[] getBuffer()
{
return mAudioBuffer;
}
public int deinitAudio()
{
if( mAudio != null )
{
mAudio.stop();
mAudio.release();
mAudio = null;
}
mAudioBuffer = null;
return 1;
}
public int initAudioThread()
{
// Make audio thread priority higher so audio thread won't get underrun
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
return 1;
}
public int pauseAudioPlayback()
{
if( mAudio != null )
{
mAudio.pause();
}
if( mRecordThread != null )
{
mRecordThread.pauseRecording();
}
return 1;
}
public int resumeAudioPlayback() if (AudioTrack.getMinBufferSize(rate, channels, encoding) > bufSize)
{ bufSize = AudioTrack.getMinBufferSize(rate, channels, encoding);
if( mAudio != null )
{
mAudio.play();
}
if( mRecordThread != null )
{
mRecordThread.resumeRecording();
}
return 1;
}
private native int nativeAudioInitJavaCallbacks(); if (Globals.AudioBufferConfig != 0) { // application's choice - use minimal buffer
bufSize = (int) ((float) bufSize * (((float) (Globals.AudioBufferConfig - 1) * 2.5f) + 1.0f));
mVirtualBufSize = bufSize;
}
mAudioBuffer = new byte[bufSize];
// ----- Audio recording ----- mAudio = new AudioTrack(AudioManager.STREAM_MUSIC,
rate,
channels,
encoding,
bufSize,
AudioTrack.MODE_STREAM);
mAudio.play();
}
return mVirtualBufSize;
}
private RecordingThread mRecordThread = null; public byte[] getBuffer() {
private AudioRecord mRecorder = null; return mAudioBuffer;
private int mRecorderBufferSize = 0; }
private byte[] startRecording(int rate, int channels, int encoding, int bufsize) public int deinitAudio() {
{ if (mAudio != null) {
if( mRecordThread == null ) mAudio.stop();
{ mAudio.release();
mRecordThread = new RecordingThread(); mAudio = null;
mRecordThread.start(); }
} mAudioBuffer = null;
if( !mRecordThread.isStopped() ) return 1;
{ }
Log.i("SDL", "SDL: error: application already opened audio recording device");
return null;
}
mRecordThread.init(bufsize); public int initAudioThread() {
// Make audio thread priority higher so audio thread won't get underrun
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
return 1;
}
int channelConfig = ( channels == 1 ) ? AudioFormat.CHANNEL_IN_MONO : public int pauseAudioPlayback() {
AudioFormat.CHANNEL_IN_STEREO; if (mAudio != null) {
int encodingConfig = ( encoding == 1 ) ? AudioFormat.ENCODING_PCM_16BIT : mAudio.pause();
AudioFormat.ENCODING_PCM_8BIT; }
if (mRecordThread != null) {
mRecordThread.pauseRecording();
}
return 1;
}
int minBufDevice = AudioRecord.getMinBufferSize(rate, channelConfig, encodingConfig); public int resumeAudioPlayback() {
int minBufferSize = Math.max(bufsize * 8, minBufDevice + (bufsize - (minBufDevice % bufsize))); if (mAudio != null) {
Log.i("SDL", "SDL: app opened recording device, rate " + rate + " channels " + channels + " sample size " + (encoding+1) + " bufsize " + bufsize + " internal bufsize " + minBufferSize); mAudio.play();
if( mRecorder == null || mRecorder.getSampleRate() != rate || }
mRecorder.getChannelCount() != channels || if (mRecordThread != null) {
mRecorder.getAudioFormat() != encodingConfig || mRecordThread.resumeRecording();
mRecorderBufferSize != minBufferSize ) }
{ return 1;
if( mRecorder != null ) }
mRecorder.release();
mRecorder = null;
try {
mRecorder = new AudioRecord(AudioSource.MIC, rate, channelConfig, encodingConfig, minBufferSize);
mRecorderBufferSize = minBufferSize;
} catch (IllegalArgumentException e) {
Log.i("SDL", "SDL: error: failed to open MIC recording device!");
try {
mRecorder = new AudioRecord(AudioSource.VOICE_RECOGNITION, rate, channelConfig, encodingConfig, minBufferSize);
mRecorderBufferSize = minBufferSize;
} catch (IllegalArgumentException eee) {
Log.i("SDL", "SDL: error: failed to open VOICE_RECOGNITION recording device!");
try {
mRecorder = new AudioRecord(AudioSource.DEFAULT, rate, channelConfig, encodingConfig, minBufferSize);
mRecorderBufferSize = minBufferSize;
} catch (IllegalArgumentException eeee) {
Log.i("SDL", "SDL: error: failed to open DEFAULT recording device!");
return null;
}
}
}
}
else
{
Log.i("SDL", "SDL: reusing old recording device");
}
mRecordThread.startRecording();
return mRecordThread.mRecordBuffer;
}
private void stopRecording() private native int nativeAudioInitJavaCallbacks();
{
if( mRecordThread == null || mRecordThread.isStopped() )
{
Log.i("SDL", "SDL: error: application already closed audio recording device");
return;
}
mRecordThread.stopRecording();
Log.i("SDL", "SDL: app closed recording device");
}
private class RecordingThread extends Thread // ----- Audio recording -----
{
private boolean stopped = true;
byte[] mRecordBuffer;
private Semaphore waitStarted = new Semaphore(0);
private boolean sleep = false;
RecordingThread() private RecordingThread mRecordThread = null;
{ private AudioRecord mRecorder = null;
super(); private int mRecorderBufferSize = 0;
}
void init(int bufsize) private byte[] startRecording(int rate, int channels, int encoding, int bufsize) {
{ if (mRecordThread == null) {
if( mRecordBuffer == null || mRecordBuffer.length != bufsize ) mRecordThread = new RecordingThread();
mRecordBuffer = new byte[bufsize]; mRecordThread.start();
} }
if (!mRecordThread.isStopped()) {
Log.i("SDL", "SDL: error: application already opened audio recording device");
return null;
}
public void run() mRecordThread.init(bufsize);
{
while( true )
{
waitStarted.acquireUninterruptibly();
waitStarted.drainPermits();
stopped = false;
sleep = false;
while( !sleep ) int channelConfig = (channels == 1) ? AudioFormat.CHANNEL_IN_MONO :
{ AudioFormat.CHANNEL_IN_STEREO;
int got = mRecorder.read(mRecordBuffer, 0, mRecordBuffer.length); int encodingConfig = (encoding == 1) ? AudioFormat.ENCODING_PCM_16BIT :
if( got != mRecordBuffer.length ) AudioFormat.ENCODING_PCM_8BIT;
{
// Audio is stopped here, sleep a bit.
try{
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
else
{
//Log.i("SDL", "SDL: nativeAudioRecordCallback with len " + mRecordBuffer.length);
nativeAudioRecordCallback();
//Log.i("SDL", "SDL: nativeAudioRecordCallback returned");
}
}
stopped = true; int minBufDevice = AudioRecord.getMinBufferSize(rate, channelConfig, encodingConfig);
mRecorder.stop(); int minBufferSize = Math.max(bufsize * 8, minBufDevice + (bufsize - (minBufDevice % bufsize)));
} Log.i("SDL", "SDL: app opened recording device, rate " + rate + " channels " + channels + " sample size " + (encoding + 1) + " bufsize " + bufsize + " internal bufsize " + minBufferSize);
} if (mRecorder == null || mRecorder.getSampleRate() != rate ||
mRecorder.getChannelCount() != channels ||
mRecorder.getAudioFormat() != encodingConfig ||
mRecorderBufferSize != minBufferSize) {
if (mRecorder != null)
mRecorder.release();
mRecorder = null;
try {
mRecorder = new AudioRecord(AudioSource.MIC, rate, channelConfig, encodingConfig, minBufferSize);
mRecorderBufferSize = minBufferSize;
} catch (IllegalArgumentException e) {
Log.i("SDL", "SDL: error: failed to open MIC recording device!");
try {
mRecorder = new AudioRecord(AudioSource.VOICE_RECOGNITION, rate, channelConfig, encodingConfig, minBufferSize);
mRecorderBufferSize = minBufferSize;
} catch (IllegalArgumentException eee) {
Log.i("SDL", "SDL: error: failed to open VOICE_RECOGNITION recording device!");
try {
mRecorder = new AudioRecord(AudioSource.DEFAULT, rate, channelConfig, encodingConfig, minBufferSize);
mRecorderBufferSize = minBufferSize;
} catch (IllegalArgumentException eeee) {
Log.i("SDL", "SDL: error: failed to open DEFAULT recording device!");
return null;
}
}
}
} else {
Log.i("SDL", "SDL: reusing old recording device");
}
mRecordThread.startRecording();
return mRecordThread.mRecordBuffer;
}
public void startRecording() private void stopRecording() {
{ if (mRecordThread == null || mRecordThread.isStopped()) {
mRecorder.startRecording(); Log.i("SDL", "SDL: error: application already closed audio recording device");
waitStarted.release(); return;
} }
public void stopRecording() mRecordThread.stopRecording();
{ Log.i("SDL", "SDL: app closed recording device");
sleep = true; }
while( !stopped )
{
try{
Thread.sleep(100);
} catch (InterruptedException e) {}
}
}
public void pauseRecording()
{
if( !stopped )
mRecorder.stop();
}
public void resumeRecording()
{
if( !stopped )
mRecorder.startRecording();
}
public boolean isStopped()
{
return stopped;
}
}
private native void nativeAudioRecordCallback(); private class RecordingThread extends Thread {
private boolean stopped = true;
byte[] mRecordBuffer;
private Semaphore waitStarted = new Semaphore(0);
private boolean sleep = false;
RecordingThread() {
super();
}
void init(int bufsize) {
if (mRecordBuffer == null || mRecordBuffer.length != bufsize)
mRecordBuffer = new byte[bufsize];
}
public void run() {
while (true) {
waitStarted.acquireUninterruptibly();
waitStarted.drainPermits();
stopped = false;
sleep = false;
while (!sleep) {
int got = mRecorder.read(mRecordBuffer, 0, mRecordBuffer.length);
if (got != mRecordBuffer.length) {
// Audio is stopped here, sleep a bit.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
} else {
//Log.i("SDL", "SDL: nativeAudioRecordCallback with len " + mRecordBuffer.length);
nativeAudioRecordCallback();
//Log.i("SDL", "SDL: nativeAudioRecordCallback returned");
}
}
stopped = true;
mRecorder.stop();
}
}
public void startRecording() {
mRecorder.startRecording();
waitStarted.release();
}
public void stopRecording() {
sleep = true;
while (!stopped) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
public void pauseRecording() {
if (!stopped)
mRecorder.stop();
}
public void resumeRecording() {
if (!stopped)
mRecorder.startRecording();
}
public boolean isStopped() {
return stopped;
}
}
private native void nativeAudioRecordCallback();
} }

View File

@ -22,119 +22,94 @@ freely, subject to the following restrictions:
package io.neoterm; package io.neoterm;
import android.os.Bundle;
import android.os.Build;
import android.os.Environment;
import android.util.DisplayMetrics;
import android.util.Log;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.AssetManager;
import android.app.Activity;
import android.view.MotionEvent;
import android.view.KeyEvent;
import android.view.InputDevice;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.ClipboardManager.OnPrimaryClipChangedListener; import android.content.ClipboardManager.OnPrimaryClipChangedListener;
import android.app.PendingIntent; import android.content.Context;
import android.app.AlarmManager; import android.os.Build;
import android.content.Intent; import android.util.Log;
import android.view.View;
import android.view.Display;
public abstract class Clipboard public abstract class Clipboard {
{ public static Clipboard get() {
public static Clipboard get() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{ return NewerClipboard.Holder.Instance;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) return OlderClipboard.Holder.Instance;
return NewerClipboard.Holder.Instance; }
return OlderClipboard.Holder.Instance;
}
public abstract void set(final Context context, final String text);
public abstract String get(final Context context);
public abstract void setListener(final Context context, final Runnable listener);
private static class NewerClipboard extends Clipboard public abstract void set(final Context context, final String text);
{
private static class Holder
{
private static final NewerClipboard Instance = new NewerClipboard();
}
public void set(final Context context, final String text)
{
try {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE);
if( clipboard != null )
clipboard.setText(text);
} catch (Exception e) {
Log.i("SDL", "setClipboardText() exception: " + e.toString());
}
}
public String get(final Context context)
{
String ret = "";
try {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE);
if( clipboard != null && clipboard.getText() != null )
ret = clipboard.getText().toString();
} catch (Exception e) {
Log.i("SDL", "getClipboardText() exception: " + e.toString());
}
return ret;
}
public void setListener(final Context context, final Runnable listener)
{
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE);
clipboard.addPrimaryClipChangedListener(new OnPrimaryClipChangedListener()
{
public void onPrimaryClipChanged()
{
listener.run();
}
});
}
}
private static class OlderClipboard extends Clipboard public abstract String get(final Context context);
{
private static class Holder public abstract void setListener(final Context context, final Runnable listener);
{
private static final OlderClipboard Instance = new OlderClipboard(); private static class NewerClipboard extends Clipboard {
} private static class Holder {
public void set(final Context context, final String text) private static final NewerClipboard Instance = new NewerClipboard();
{ }
try {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE); public void set(final Context context, final String text) {
if( clipboard != null ) try {
clipboard.setText(text); ClipboardManager clipboard = (ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE);
} catch (Exception e) { if (clipboard != null)
Log.i("SDL", "setClipboardText() exception: " + e.toString()); clipboard.setText(text);
} } catch (Exception e) {
} Log.i("SDL", "setClipboardText() exception: " + e.toString());
public String get(final Context context) }
{ }
String ret = "";
try { public String get(final Context context) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE); String ret = "";
if( clipboard != null && clipboard.getText() != null ) try {
ret = clipboard.getText().toString(); ClipboardManager clipboard = (ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE);
} catch (Exception e) { if (clipboard != null && clipboard.getText() != null)
Log.i("SDL", "getClipboardText() exception: " + e.toString()); ret = clipboard.getText().toString();
} } catch (Exception e) {
return ret; Log.i("SDL", "getClipboardText() exception: " + e.toString());
} }
public void setListener(final Context context, final Runnable listener) return ret;
{ }
Log.i("SDL", "Cannot set clipboard listener on Android 2.3 or older");
} public void setListener(final Context context, final Runnable listener) {
} ClipboardManager clipboard = (ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE);
clipboard.addPrimaryClipChangedListener(new OnPrimaryClipChangedListener() {
public void onPrimaryClipChanged() {
listener.run();
}
});
}
}
private static class OlderClipboard extends Clipboard {
private static class Holder {
private static final OlderClipboard Instance = new OlderClipboard();
}
public void set(final Context context, final String text) {
try {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE);
if (clipboard != null)
clipboard.setText(text);
} catch (Exception e) {
Log.i("SDL", "setClipboardText() exception: " + e.toString());
}
}
public String get(final Context context) {
String ret = "";
try {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE);
if (clipboard != null && clipboard.getText() != null)
ret = clipboard.getText().toString();
} catch (Exception e) {
Log.i("SDL", "getClipboardText() exception: " + e.toString());
}
return ret;
}
public void setListener(final Context context, final Runnable listener) {
Log.i("SDL", "Cannot set clipboard listener on Android 2.3 or older");
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -25,124 +25,124 @@ package io.neoterm;
import android.view.KeyEvent; import android.view.KeyEvent;
public class Globals { public class Globals {
public static String XLIB_DIR = "/data/data/io.neoterm/files/usr/lib/xorg-neoterm"; public static String XLIB_DIR = "/data/data/io.neoterm/files/usr/lib/xorg-neoterm";
public static String XLIBS[] = { public static String XLIBS[] = {
"x11_sdl_native_helpers", "x11_sdl_native_helpers",
"x11_sdl-1.2", "x11_sdl-1.2",
"x11_sdl_ttf", "x11_sdl_ttf",
"x11_crypto", "x11_crypto",
}; };
public static String XAPP_LIBS[] = { public static String XAPP_LIBS[] = {
"x11_application", "x11_application",
"x11_sdl_main", "x11_sdl_main",
}; };
// These config options are modified by ChangeAppsettings.sh script - see the detailed descriptions there // These config options are modified by ChangeAppsettings.sh script - see the detailed descriptions there
public static String AppLibraries[] = {"sdl_native_helpers", "sdl-1.2", "sdl_ttf", "crypto"}; public static String AppLibraries[] = {"sdl_native_helpers", "sdl-1.2", "sdl_ttf", "crypto"};
public static final boolean Using_SDL_1_3 = false; public static final boolean Using_SDL_1_3 = false;
public static final boolean Using_SDL_2_0 = false; public static final boolean Using_SDL_2_0 = false;
public static String[] DataDownloadUrl = {"!!Data files|:data.tar.gz:data-1.tgz", "!!Data files|:DroidSansMono.ttf:DroidSansMono.ttf", "Additional fonts (90Mb)|:xfonts.tar.gz:http://sourceforge.net/projects/libsdl-android/files/apk/XServer-XSDL/xfonts.tgz/download",}; public static String[] DataDownloadUrl = {"!!Data files|:data.tar.gz:data-1.tgz", "!!Data files|:DroidSansMono.ttf:DroidSansMono.ttf", "Additional fonts (90Mb)|:xfonts.tar.gz:http://sourceforge.net/projects/libsdl-android/files/apk/XServer-XSDL/xfonts.tgz/download",};
public static boolean SwVideoMode = true; public static boolean SwVideoMode = true;
public static boolean NeedDepthBuffer = false; public static boolean NeedDepthBuffer = false;
public static boolean NeedStencilBuffer = false; public static boolean NeedStencilBuffer = false;
public static boolean NeedGles2 = false; public static boolean NeedGles2 = false;
public static boolean NeedGles3 = false; public static boolean NeedGles3 = false;
public static boolean CompatibilityHacksVideo = false; public static boolean CompatibilityHacksVideo = false;
public static boolean CompatibilityHacksForceScreenUpdateMouseClick = true; public static boolean CompatibilityHacksForceScreenUpdateMouseClick = true;
public static boolean CompatibilityHacksStaticInit = false; public static boolean CompatibilityHacksStaticInit = false;
public static boolean CompatibilityHacksTextInputEmulatesHwKeyboard = true; public static boolean CompatibilityHacksTextInputEmulatesHwKeyboard = true;
public static int TextInputKeyboard = 0; public static int TextInputKeyboard = 0;
public static boolean KeepAspectRatioDefaultSetting = false; public static boolean KeepAspectRatioDefaultSetting = false;
public static boolean InhibitSuspend = true; public static boolean InhibitSuspend = true;
public static boolean CreateService = true; public static boolean CreateService = true;
public static String ReadmeText = ""; public static String ReadmeText = "";
public static String CommandLine = "XSDL"; public static String CommandLine = "XSDL";
public static boolean AppUsesMouse = true; public static boolean AppUsesMouse = true;
public static boolean AppNeedsTwoButtonMouse = true; public static boolean AppNeedsTwoButtonMouse = true;
public static boolean RightMouseButtonLongPress = false; public static boolean RightMouseButtonLongPress = false;
public static boolean ForceRelativeMouseMode = true; // If both on-screen keyboard and mouse are needed, this will only set the default setting, user may override it later public static boolean ForceRelativeMouseMode = true; // If both on-screen keyboard and mouse are needed, this will only set the default setting, user may override it later
public static boolean ShowMouseCursor = false; // Draw system mouse cursor, if the app does not do it public static boolean ShowMouseCursor = false; // Draw system mouse cursor, if the app does not do it
public static boolean ScreenFollowsMouse = true; // Move app screen make mouse cursor always visible, when soft keyboard is shown public static boolean ScreenFollowsMouse = true; // Move app screen make mouse cursor always visible, when soft keyboard is shown
public static boolean AppNeedsArrowKeys = false; public static boolean AppNeedsArrowKeys = false;
public static boolean AppNeedsTextInput = false; public static boolean AppNeedsTextInput = false;
public static boolean AppUsesJoystick = false; public static boolean AppUsesJoystick = false;
public static boolean AppUsesSecondJoystick = false; public static boolean AppUsesSecondJoystick = false;
public static boolean AppUsesThirdJoystick = false; public static boolean AppUsesThirdJoystick = false;
public static boolean AppUsesAccelerometer = false; public static boolean AppUsesAccelerometer = false;
public static boolean AppUsesGyroscope = false; public static boolean AppUsesGyroscope = false;
public static boolean AppUsesOrientationSensor = false; public static boolean AppUsesOrientationSensor = false;
public static boolean AppUsesMultitouch = true; public static boolean AppUsesMultitouch = true;
public static boolean NonBlockingSwapBuffers = false; public static boolean NonBlockingSwapBuffers = false;
public static boolean ResetSdlConfigForThisVersion = false; public static boolean ResetSdlConfigForThisVersion = false;
public static String DeleteFilesOnUpgrade = "%"; public static String DeleteFilesOnUpgrade = "%";
public static int AppTouchscreenKeyboardKeysAmount = 3; public static int AppTouchscreenKeyboardKeysAmount = 3;
public static String[] AppTouchscreenKeyboardKeysNames = "LCTRL LALT LSHIFT RETURN SPACE DELETE KP_PLUS KP_MINUS 1 2".split(" "); public static String[] AppTouchscreenKeyboardKeysNames = "LCTRL LALT LSHIFT RETURN SPACE DELETE KP_PLUS KP_MINUS 1 2".split(" ");
public static int StartupMenuButtonTimeout = 3000; public static int StartupMenuButtonTimeout = 3000;
public static int AppMinimumRAM = 0; public static int AppMinimumRAM = 0;
public static SettingsMenu.Menu HiddenMenuOptions[] = {}; // If you see error here - update HiddenMenuOptions in your AndroidAppSettings.cfg: change OptionalDownloadConfig to SettingsMenuMisc.OptionalDownloadConfig etc. public static SettingsMenu.Menu HiddenMenuOptions[] = {}; // If you see error here - update HiddenMenuOptions in your AndroidAppSettings.cfg: change OptionalDownloadConfig to SettingsMenuMisc.OptionalDownloadConfig etc.
public static SettingsMenu.Menu FirstStartMenuOptions[] = {new SettingsMenuMisc.GyroscopeCalibration(), new SettingsMenuMisc.OptionalDownloadConfig(),}; public static SettingsMenu.Menu FirstStartMenuOptions[] = {new SettingsMenuMisc.GyroscopeCalibration(), new SettingsMenuMisc.OptionalDownloadConfig(),};
// Phone-specific config, modified by user in "Change phone config" startup dialog // Phone-specific config, modified by user in "Change phone config" startup dialog
public static int VideoDepthBpp = 16; public static int VideoDepthBpp = 16;
public static boolean HorizontalOrientation = true; public static boolean HorizontalOrientation = true;
public static boolean AutoDetectOrientation = false; public static boolean AutoDetectOrientation = false;
public static boolean ImmersiveMode = true; public static boolean ImmersiveMode = true;
public static boolean HideSystemMousePointer = false; public static boolean HideSystemMousePointer = false;
public static boolean DownloadToSdcard = true; public static boolean DownloadToSdcard = true;
public static boolean PhoneHasArrowKeys = false; public static boolean PhoneHasArrowKeys = false;
public static boolean UseAccelerometerAsArrowKeys = false; public static boolean UseAccelerometerAsArrowKeys = false;
public static boolean UseTouchscreenKeyboard = true; public static boolean UseTouchscreenKeyboard = true;
public static int TouchscreenKeyboardSize = 1; public static int TouchscreenKeyboardSize = 1;
public static final int TOUCHSCREEN_KEYBOARD_CUSTOM = 4; public static final int TOUCHSCREEN_KEYBOARD_CUSTOM = 4;
public static int TouchscreenKeyboardDrawSize = 2; public static int TouchscreenKeyboardDrawSize = 2;
public static int TouchscreenKeyboardTheme = 0; public static int TouchscreenKeyboardTheme = 0;
public static int TouchscreenKeyboardTransparency = 2; public static int TouchscreenKeyboardTransparency = 2;
public static boolean FloatingScreenJoystick = false; public static boolean FloatingScreenJoystick = false;
public static int AccelerometerSensitivity = 2; public static int AccelerometerSensitivity = 2;
public static int AccelerometerCenterPos = 2; public static int AccelerometerCenterPos = 2;
public static int AudioBufferConfig = 0; public static int AudioBufferConfig = 0;
public static boolean OptionalDataDownload[] = null; public static boolean OptionalDataDownload[] = null;
public static int LeftClickMethod = ForceRelativeMouseMode ? Mouse.LEFT_CLICK_WITH_TAP_OR_TIMEOUT : Mouse.LEFT_CLICK_NORMAL; public static int LeftClickMethod = ForceRelativeMouseMode ? Mouse.LEFT_CLICK_WITH_TAP_OR_TIMEOUT : Mouse.LEFT_CLICK_NORMAL;
public static int LeftClickKey = KeyEvent.KEYCODE_DPAD_CENTER; public static int LeftClickKey = KeyEvent.KEYCODE_DPAD_CENTER;
public static int LeftClickTimeout = 3; public static int LeftClickTimeout = 3;
public static int RightClickTimeout = 4; public static int RightClickTimeout = 4;
public static int RightClickMethod = AppNeedsTwoButtonMouse ? Mouse.RIGHT_CLICK_WITH_MULTITOUCH : Mouse.RIGHT_CLICK_NONE; public static int RightClickMethod = AppNeedsTwoButtonMouse ? Mouse.RIGHT_CLICK_WITH_MULTITOUCH : Mouse.RIGHT_CLICK_NONE;
public static int RightClickKey = KeyEvent.KEYCODE_MENU; public static int RightClickKey = KeyEvent.KEYCODE_MENU;
public static boolean MoveMouseWithJoystick = false; public static boolean MoveMouseWithJoystick = false;
public static int MoveMouseWithJoystickSpeed = 1; public static int MoveMouseWithJoystickSpeed = 1;
public static int MoveMouseWithJoystickAccel = 0; public static int MoveMouseWithJoystickAccel = 0;
public static boolean MoveMouseWithGyroscope = true; public static boolean MoveMouseWithGyroscope = true;
public static int MoveMouseWithGyroscopeSpeed = 2; public static int MoveMouseWithGyroscopeSpeed = 2;
public static boolean ClickMouseWithDpad = false; public static boolean ClickMouseWithDpad = false;
public static boolean RelativeMouseMovement = ForceRelativeMouseMode; // Laptop touchpad mode public static boolean RelativeMouseMovement = ForceRelativeMouseMode; // Laptop touchpad mode
public static boolean ForceHardwareMouse = false; public static boolean ForceHardwareMouse = false;
public static int RelativeMouseMovementSpeed = 2; public static int RelativeMouseMovementSpeed = 2;
public static int RelativeMouseMovementAccel = 0; public static int RelativeMouseMovementAccel = 0;
public static int ShowScreenUnderFinger = Mouse.ZOOM_NONE; public static int ShowScreenUnderFinger = Mouse.ZOOM_NONE;
public static int ClickScreenPressure = 0; public static int ClickScreenPressure = 0;
public static int ClickScreenTouchspotSize = 0; public static int ClickScreenTouchspotSize = 0;
public static boolean FingerHover = true; public static boolean FingerHover = true;
public static boolean HoverJitterFilter = true; public static boolean HoverJitterFilter = true;
public static boolean GenerateSubframeTouchEvents = false; public static boolean GenerateSubframeTouchEvents = false;
public static boolean KeepAspectRatio = KeepAspectRatioDefaultSetting; public static boolean KeepAspectRatio = KeepAspectRatioDefaultSetting;
public static boolean TvBorders = true; public static boolean TvBorders = true;
public static int RemapHwKeycode[] = new int[SDL_Keys.JAVA_KEYCODE_LAST]; public static int RemapHwKeycode[] = new int[SDL_Keys.JAVA_KEYCODE_LAST];
public static int RemapScreenKbKeycode[] = new int[6]; public static int RemapScreenKbKeycode[] = new int[6];
public static int ScreenKbControlsLayout[][] = AppUsesThirdJoystick ? // Values for 800x480 resolution public static int ScreenKbControlsLayout[][] = AppUsesThirdJoystick ? // Values for 800x480 resolution
new int[][]{{0, 303, 177, 480}, {0, 0, 48, 48}, {400, 392, 488, 480}, {312, 392, 400, 480}, {400, 304, 488, 392}, {312, 304, 400, 392}, {400, 216, 488, 304}, {312, 216, 400, 304}, {623, 303, 800, 480}, {623, 126, 800, 303}} : new int[][]{{0, 303, 177, 480}, {0, 0, 48, 48}, {400, 392, 488, 480}, {312, 392, 400, 480}, {400, 304, 488, 392}, {312, 304, 400, 392}, {400, 216, 488, 304}, {312, 216, 400, 304}, {623, 303, 800, 480}, {623, 126, 800, 303}} :
AppUsesSecondJoystick ? AppUsesSecondJoystick ?
new int[][]{{0, 303, 177, 480}, {0, 0, 48, 48}, {400, 392, 488, 480}, {312, 392, 400, 480}, {400, 304, 488, 392}, {312, 304, 400, 392}, {400, 216, 488, 304}, {312, 216, 400, 304}, {623, 303, 800, 480}} : new int[][]{{0, 303, 177, 480}, {0, 0, 48, 48}, {400, 392, 488, 480}, {312, 392, 400, 480}, {400, 304, 488, 392}, {312, 304, 400, 392}, {400, 216, 488, 304}, {312, 216, 400, 304}, {623, 303, 800, 480}} :
new int[][]{{0, 303, 177, 480}, {0, 0, 48, 48}, {712, 392, 800, 480}, {624, 392, 712, 480}, {712, 304, 800, 392}, {624, 304, 712, 392}, {712, 216, 800, 304}, {624, 216, 712, 304}}; new int[][]{{0, 303, 177, 480}, {0, 0, 48, 48}, {712, 392, 800, 480}, {624, 392, 712, 480}, {712, 304, 800, 392}, {624, 304, 712, 392}, {712, 216, 800, 304}, {624, 216, 712, 304}};
public static boolean ScreenKbControlsShown[] = new boolean[ScreenKbControlsLayout.length]; /* Also joystick and text input button added */ public static boolean ScreenKbControlsShown[] = new boolean[ScreenKbControlsLayout.length]; /* Also joystick and text input button added */
public static int RemapMultitouchGestureKeycode[] = new int[4]; public static int RemapMultitouchGestureKeycode[] = new int[4];
public static boolean MultitouchGesturesUsed[] = new boolean[4]; public static boolean MultitouchGesturesUsed[] = new boolean[4];
public static int MultitouchGestureSensitivity = 1; public static int MultitouchGestureSensitivity = 1;
public static int TouchscreenCalibration[] = new int[4]; public static int TouchscreenCalibration[] = new int[4];
public static String DataDir = "/data/data/io.neoterm/files/usr/share/xorg-neoterm"; public static String DataDir = "/data/data/io.neoterm/files/usr/share/xorg-neoterm";
public static String UnSecureDataDir = "/data/data/io.neoterm/files/usr/share/xorg-neoterm"; public static String UnSecureDataDir = "/data/data/io.neoterm/files/usr/share/xorg-neoterm";
public static String HomeDir = "/data/data/io.neoterm/files/home"; public static String HomeDir = "/data/data/io.neoterm/files/home";
public static boolean VideoLinearFilter = true; public static boolean VideoLinearFilter = true;
public static boolean MultiThreadedVideo = false; public static boolean MultiThreadedVideo = false;
public static boolean OuyaEmulation = false; // For debugging public static boolean OuyaEmulation = false; // For debugging
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7,11 +7,11 @@ import android.content.Context;
*/ */
public class NeoAccelerometerReader extends AccelerometerReader { public class NeoAccelerometerReader extends AccelerometerReader {
public NeoAccelerometerReader(Context context) { public NeoAccelerometerReader(Context context) {
super(context); super(context);
} }
public static void setGyroInvertedOrientation(boolean invertedOrientation) { public static void setGyroInvertedOrientation(boolean invertedOrientation) {
gyro.invertedOrientation = invertedOrientation; gyro.invertedOrientation = invertedOrientation;
} }
} }

View File

@ -7,7 +7,7 @@ import io.neoterm.xorg.NeoXorgViewClient;
*/ */
public class NeoAudioThread extends AudioThread { public class NeoAudioThread extends AudioThread {
public NeoAudioThread(NeoXorgViewClient client) { public NeoAudioThread(NeoXorgViewClient client) {
super(client); super(client);
} }
} }

View File

@ -7,15 +7,15 @@ import io.neoterm.xorg.NeoXorgViewClient;
*/ */
public class NeoGLView extends DemoGLSurfaceView { public class NeoGLView extends DemoGLSurfaceView {
public NeoGLView(NeoXorgViewClient client) { public NeoGLView(NeoXorgViewClient client) {
super(client); super(client);
} }
public void callNativeScreenKeyboardShown(int shown) { public void callNativeScreenKeyboardShown(int shown) {
nativeScreenKeyboardShown(shown); nativeScreenKeyboardShown(shown);
} }
public void callNativeScreenVisibleRect(int x, int y, int w, int h) { public void callNativeScreenVisibleRect(int x, int y, int w, int h) {
nativeScreenVisibleRect(x, y, w, h); nativeScreenVisibleRect(x, y, w, h);
} }
} }

View File

@ -5,11 +5,11 @@ package io.neoterm;
*/ */
public class NeoRenderer { public class NeoRenderer {
public static void callNativeTextInputFinished() { public static void callNativeTextInputFinished() {
DemoRenderer.nativeTextInputFinished(); DemoRenderer.nativeTextInputFinished();
} }
public static void callNativeTextInput(int ascii, int unicode) { public static void callNativeTextInput(int ascii, int unicode) {
DemoRenderer.nativeTextInput(ascii, unicode); DemoRenderer.nativeTextInput(ascii, unicode);
} }
} }

View File

@ -7,11 +7,11 @@ import io.neoterm.xorg.R;
*/ */
public class NeoTextInput { public class NeoTextInput {
public static int TextInputKeyboardList[][] = public static int TextInputKeyboardList[][] =
{ {
{0, R.xml.qwerty, R.xml.c64, R.xml.amiga, R.xml.atari800}, {0, R.xml.qwerty, R.xml.c64, R.xml.amiga, R.xml.atari800},
{0, R.xml.qwerty_shift, R.xml.c64, R.xml.amiga_shift, R.xml.atari800}, {0, R.xml.qwerty_shift, R.xml.c64, R.xml.amiga_shift, R.xml.atari800},
{0, R.xml.qwerty_alt, R.xml.c64, R.xml.amiga_alt, R.xml.atari800}, {0, R.xml.qwerty_alt, R.xml.c64, R.xml.amiga_alt, R.xml.atari800},
{0, R.xml.qwerty_alt_shift, R.xml.c64, R.xml.amiga_alt_shift, R.xml.atari800} {0, R.xml.qwerty_alt_shift, R.xml.c64, R.xml.amiga_alt_shift, R.xml.atari800}
}; };
} }

View File

@ -7,7 +7,7 @@ import io.neoterm.xorg.NeoXorgViewClient;
*/ */
public class NeoXorgSettings { public class NeoXorgSettings {
public static void init(NeoXorgViewClient client) { public static void init(NeoXorgViewClient client) {
Settings.Load(client); Settings.Load(client);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -24,189 +24,160 @@ package io.neoterm;
import android.content.DialogInterface; import android.content.DialogInterface;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import io.neoterm.xorg.R;
import java.util.ArrayList; import java.util.ArrayList;
import io.neoterm.xorg.R;
class SettingsMenu {
public static abstract class Menu {
// Should be overridden by children
abstract void run(final MainActivity p);
class SettingsMenu abstract String title(final MainActivity p);
{
public static abstract class Menu
{
// Should be overridden by children
abstract void run(final MainActivity p);
abstract String title(final MainActivity p);
boolean enabled()
{
return true;
}
// Should not be overridden
boolean enabledOrHidden()
{
for( Menu m: Globals.HiddenMenuOptions )
{
if( m.getClass().getName().equals( this.getClass().getName() ) )
return false;
}
return enabled();
}
void showMenuOptionsList(final MainActivity p, final Menu[] list)
{
menuStack.add(this);
ArrayList<CharSequence> items = new ArrayList<CharSequence> ();
for( Menu m: list )
{
if(m.enabledOrHidden())
items.add(m.title(p));
}
AlertDialog.Builder builder = new AlertDialog.Builder(p);
builder.setTitle(title(p));
builder.setItems(items.toArray(new CharSequence[0]), new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int item)
{
dialog.dismiss();
int selected = 0;
for( Menu m: list ) boolean enabled() {
{ return true;
if(m.enabledOrHidden()) }
{
if( selected == item )
{
m.run(p);
return;
}
selected ++;
}
}
}
});
builder.setOnCancelListener(new DialogInterface.OnCancelListener()
{
public void onCancel(DialogInterface dialog)
{
goBackOuterMenu(p);
}
});
AlertDialog alert = builder.create();
alert.setOwnerActivity(p);
alert.show();
}
}
static ArrayList<Menu> menuStack = new ArrayList<Menu> (); // Should not be overridden
boolean enabledOrHidden() {
for (Menu m : Globals.HiddenMenuOptions) {
if (m.getClass().getName().equals(this.getClass().getName()))
return false;
}
return enabled();
}
public static void showConfig(final MainActivity p, final boolean firstStart) void showMenuOptionsList(final MainActivity p, final Menu[] list) {
{ menuStack.add(this);
Settings.settingsChanged = true; ArrayList<CharSequence> items = new ArrayList<CharSequence>();
if( Globals.OptionalDataDownload == null ) for (Menu m : list) {
{ if (m.enabledOrHidden())
String downloads[] = Globals.DataDownloadUrl; items.add(m.title(p));
Globals.OptionalDataDownload = new boolean[downloads.length]; }
boolean oldFormat = true; AlertDialog.Builder builder = new AlertDialog.Builder(p);
for( int i = 0; i < downloads.length; i++ ) builder.setTitle(title(p));
{ builder.setItems(items.toArray(new CharSequence[0]), new DialogInterface.OnClickListener() {
if( downloads[i].indexOf("!") == 0 ) public void onClick(DialogInterface dialog, int item) {
{ dialog.dismiss();
Globals.OptionalDataDownload[i] = true; int selected = 0;
oldFormat = false;
}
}
if( oldFormat )
Globals.OptionalDataDownload[0] = true;
}
if(!firstStart) for (Menu m : list) {
new MainMenu().run(p); if (m.enabledOrHidden()) {
else if (selected == item) {
{ m.run(p);
if( Globals.StartupMenuButtonTimeout > 0 ) // If we did not disable startup menu altogether return;
{ }
for( Menu m: Globals.FirstStartMenuOptions ) selected++;
{ }
boolean hidden = false; }
for( Menu m1: Globals.HiddenMenuOptions ) }
{ });
if( m1.getClass().getName().equals( m.getClass().getName() ) ) builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
hidden = true; public void onCancel(DialogInterface dialog) {
} goBackOuterMenu(p);
if( ! hidden ) }
menuStack.add(0, m); });
} AlertDialog alert = builder.create();
} alert.setOwnerActivity(p);
goBack(p); alert.show();
} }
} }
static void goBack(final MainActivity p) static ArrayList<Menu> menuStack = new ArrayList<Menu>();
{
if(menuStack.isEmpty())
{
Settings.Save(p);
}
else
{
Menu c = menuStack.remove(menuStack.size() - 1);
c.run(p);
}
}
static void goBackOuterMenu(final MainActivity p) public static void showConfig(final MainActivity p, final boolean firstStart) {
{ Settings.settingsChanged = true;
if(!menuStack.isEmpty()) if (Globals.OptionalDataDownload == null) {
menuStack.remove(menuStack.size() - 1); String downloads[] = Globals.DataDownloadUrl;
goBack(p); Globals.OptionalDataDownload = new boolean[downloads.length];
} boolean oldFormat = true;
for (int i = 0; i < downloads.length; i++) {
static class OkButton extends Menu if (downloads[i].indexOf("!") == 0) {
{ Globals.OptionalDataDownload[i] = true;
String title(final MainActivity p) oldFormat = false;
{ }
return p.getResources().getString(R.string.ok); }
} if (oldFormat)
void run (final MainActivity p) Globals.OptionalDataDownload[0] = true;
{ }
goBackOuterMenu(p);
}
}
static class DummyMenu extends Menu if (!firstStart)
{ new MainMenu().run(p);
String title(final MainActivity p) else {
{ if (Globals.StartupMenuButtonTimeout > 0) // If we did not disable startup menu altogether
return p.getResources().getString(R.string.ok); {
} for (Menu m : Globals.FirstStartMenuOptions) {
void run (final MainActivity p) boolean hidden = false;
{ for (Menu m1 : Globals.HiddenMenuOptions) {
goBack(p); if (m1.getClass().getName().equals(m.getClass().getName()))
} hidden = true;
} }
if (!hidden)
menuStack.add(0, m);
}
}
goBack(p);
}
}
static class MainMenu extends Menu static void goBack(final MainActivity p) {
{ if (menuStack.isEmpty()) {
String title(final MainActivity p) Settings.Save(p);
{ } else {
return p.getResources().getString(R.string.device_config); Menu c = menuStack.remove(menuStack.size() - 1);
} c.run(p);
void run (final MainActivity p) }
{ }
Menu options[] =
{ static void goBackOuterMenu(final MainActivity p) {
new SettingsMenuMisc.DownloadConfig(), if (!menuStack.isEmpty())
new SettingsMenuMisc.OptionalDownloadConfig(false), menuStack.remove(menuStack.size() - 1);
new SettingsMenuKeyboard.KeyboardConfigMainMenu(), goBack(p);
new SettingsMenuMouse.MouseConfigMainMenu(), }
new SettingsMenuMisc.AudioConfig(),
new SettingsMenuKeyboard.RemapHwKeysConfig(), static class OkButton extends Menu {
new SettingsMenuKeyboard.ScreenGesturesConfig(), String title(final MainActivity p) {
new SettingsMenuMisc.VideoSettingsConfig(), return p.getResources().getString(R.string.ok);
new SettingsMenuMisc.CommandlineConfig(), }
new SettingsMenuMisc.ResetToDefaultsConfig(),
new OkButton(), void run(final MainActivity p) {
}; goBackOuterMenu(p);
showMenuOptionsList(p, options); }
} }
}
static class DummyMenu extends Menu {
String title(final MainActivity p) {
return p.getResources().getString(R.string.ok);
}
void run(final MainActivity p) {
goBack(p);
}
}
static class MainMenu extends Menu {
String title(final MainActivity p) {
return p.getResources().getString(R.string.device_config);
}
void run(final MainActivity p) {
Menu options[] =
{
new SettingsMenuMisc.DownloadConfig(),
new SettingsMenuMisc.OptionalDownloadConfig(false),
new SettingsMenuKeyboard.KeyboardConfigMainMenu(),
new SettingsMenuMouse.MouseConfigMainMenu(),
new SettingsMenuMisc.AudioConfig(),
new SettingsMenuKeyboard.RemapHwKeysConfig(),
new SettingsMenuKeyboard.ScreenGesturesConfig(),
new SettingsMenuMisc.VideoSettingsConfig(),
new SettingsMenuMisc.CommandlineConfig(),
new SettingsMenuMisc.ResetToDefaultsConfig(),
new OkButton(),
};
showMenuOptionsList(p, options);
}
}
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,11 +22,8 @@ freely, subject to the following restrictions:
package io.neoterm; package io.neoterm;
import java.io.InputStream;
import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.EOFException; import java.io.InputStream;
import android.util.Log;
/** /**
* Decompresses a .xz file in streamed mode (no seeking). * Decompresses a .xz file in streamed mode (no seeking).
@ -34,128 +31,114 @@ import android.util.Log;
* but using liblzma and JNI instead of Java, because Java heap * but using liblzma and JNI instead of Java, because Java heap
* is very limited, and we're hitting memory limit on emulator. * is very limited, and we're hitting memory limit on emulator.
*/ */
public class XZInputStream extends InputStream public class XZInputStream extends InputStream {
{ private long nativeData = 0;
private long nativeData = 0; private InputStream in = null;
private InputStream in = null; private final byte[] inBuf = new byte[8192];
private final byte[] inBuf = new byte[8192]; private int inOffset = 0;
private int inOffset = 0; private int inAvailable = 0;
private int inAvailable = 0; private boolean outBufEof = false;
private boolean outBufEof = false; private int offsets[] = new int[2];
private int offsets[] = new int[2];
private final byte[] tempBuf = new byte[1]; private final byte[] tempBuf = new byte[1];
public XZInputStream(InputStream in) throws IOException public XZInputStream(InputStream in) throws IOException {
{ this.in = in;
this.in = in; if (in == null) {
if (in == null) throw new NullPointerException("InputStream == null");
{ }
throw new NullPointerException("InputStream == null"); nativeData = nativeInit();
} if (nativeData == 0) {
nativeData = nativeInit(); throw new OutOfMemoryError("Cannot initialize JNI liblzma object");
if (nativeData == 0) }
{ }
throw new OutOfMemoryError("Cannot initialize JNI liblzma object");
}
}
@Override @Override
public int available() throws IOException public int available() throws IOException {
{ return 0; // Don't care
return 0; // Don't care }
}
@Override @Override
public void close() throws IOException public void close() throws IOException {
{ synchronized (this) {
synchronized (this) if (nativeData != 0)
{ nativeClose(nativeData);
if (nativeData != 0) nativeData = 0;
nativeClose(nativeData); if (in != null) {
nativeData = 0; try {
if (in != null) in.close();
{ } finally {
try { in = null;
in.close(); }
} finally { }
in = null; }
} }
}
}
}
@Override @Override
protected void finalize() throws IOException protected void finalize() throws IOException {
{ try {
try { close();
close(); } finally {
} finally { try {
try { super.finalize();
super.finalize(); } catch (Throwable t) {
} catch (Throwable t) { throw new AssertionError(t);
throw new AssertionError(t); }
} }
} }
}
@Override @Override
public int read() throws IOException public int read() throws IOException {
{ return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF); }
}
@Override @Override
public int read(byte[] outBuf, int outOffset, int outCount) throws IOException public int read(byte[] outBuf, int outOffset, int outCount) throws IOException {
{ //Log.i("SDL", "XZInputStream.read: outOffset " + outOffset + " outCount " + outCount + " outBufEof " + outBufEof +
//Log.i("SDL", "XZInputStream.read: outOffset " + outOffset + " outCount " + outCount + " outBufEof " + outBufEof + // " inOffset " + inOffset + " inAvailable " + inAvailable);
// " inOffset " + inOffset + " inAvailable " + inAvailable); if (outBufEof)
if (outBufEof) return -1;
return -1; if (outCount <= 0)
if (outCount <= 0) return 0;
return 0;
int oldOutOffset = outOffset; int oldOutOffset = outOffset;
if (inOffset >= inAvailable && inAvailable != -1) if (inOffset >= inAvailable && inAvailable != -1) {
{ inAvailable = in.read(inBuf, 0, inBuf.length);
inAvailable = in.read(inBuf, 0, inBuf.length); inOffset = 0;
inOffset = 0; //Log.i("SDL", "XZInputStream.read: in.read: inOffset " + inOffset + " inAvailable " + inAvailable);
//Log.i("SDL", "XZInputStream.read: in.read: inOffset " + inOffset + " inAvailable " + inAvailable); }
}
offsets[0] = inOffset; offsets[0] = inOffset;
offsets[1] = outOffset; offsets[1] = outOffset;
int ret = nativeRead(nativeData, inBuf, inAvailable, outBuf, outCount, offsets); int ret = nativeRead(nativeData, inBuf, inAvailable, outBuf, outCount, offsets);
inOffset = offsets[0]; inOffset = offsets[0];
outOffset = offsets[1]; outOffset = offsets[1];
//Log.i("SDL", "XZInputStream.read: nativeRead: outOffset " + outOffset + " outCount " + outCount + " outBufEof " + outBufEof + //Log.i("SDL", "XZInputStream.read: nativeRead: outOffset " + outOffset + " outCount " + outCount + " outBufEof " + outBufEof +
// " inOffset " + inOffset + " inAvailable " + inAvailable + " ret " + ret); // " inOffset " + inOffset + " inAvailable " + inAvailable + " ret " + ret);
if (ret != 0) if (ret != 0) {
{ if (ret == 1) {
if (ret == 1) if (inOffset < inAvailable)
{ throw new IOException("Garbage at the end of LZMA stream");
if (inOffset < inAvailable) if (inAvailable != -1)
throw new IOException("Garbage at the end of LZMA stream"); inAvailable = in.read(inBuf, 0, inBuf.length);
if (inAvailable != -1) if (inAvailable != -1)
inAvailable = in.read(inBuf, 0, inBuf.length); throw new IOException("Garbage at the end of LZMA stream");
if (inAvailable != -1) outBufEof = true;
throw new IOException("Garbage at the end of LZMA stream"); } else {
outBufEof = true; throw new IOException("LZMA error " + ret);
} }
else }
{
throw new IOException("LZMA error " + ret);
}
}
//Log.i("SDL", "XZInputStream.read: returning " + (outOffset - oldOutOffset)); //Log.i("SDL", "XZInputStream.read: returning " + (outOffset - oldOutOffset));
return outOffset - oldOutOffset; return outOffset - oldOutOffset;
} }
private native long nativeInit(); private native long nativeInit();
private native void nativeClose(long nativeData);
private native int nativeRead(long nativeData, byte[] inBuf, int inAvailable, byte[] outBuf, int outCount, int[] offsets); private native void nativeClose(long nativeData);
private native int nativeRead(long nativeData, byte[] inBuf, int inAvailable, byte[] outBuf, int outCount, int[] offsets);
} }

View File

@ -3,7 +3,6 @@ package io.neoterm.xorg;
import android.content.Context; import android.content.Context;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import io.neoterm.NeoGLView; import io.neoterm.NeoGLView;
/** /**
@ -11,35 +10,35 @@ import io.neoterm.NeoGLView;
*/ */
public interface NeoXorgViewClient { public interface NeoXorgViewClient {
Context getContext(); Context getContext();
boolean isKeyboardWithoutTextInputShown(); boolean isKeyboardWithoutTextInputShown();
void showScreenKeyboardWithoutTextInputField(int flags); void showScreenKeyboardWithoutTextInputField(int flags);
void setScreenKeyboardHintMessage(String hideMessage); void setScreenKeyboardHintMessage(String hideMessage);
boolean isScreenKeyboardShown(); boolean isScreenKeyboardShown();
void showScreenKeyboard(String message); void showScreenKeyboard(String message);
void hideScreenKeyboard(); void hideScreenKeyboard();
void runOnUiThread(Runnable runnable); void runOnUiThread(Runnable runnable);
void updateScreenOrientation(); void updateScreenOrientation();
void initScreenOrientation(); void initScreenOrientation();
boolean isRunningOnOUYA(); boolean isRunningOnOUYA();
NeoGLView getGLView(); NeoGLView getGLView();
Window getWindow(); Window getWindow();
WindowManager getWindowManager(); WindowManager getWindowManager();
void setSystemMousePointerVisible(int visible); void setSystemMousePointerVisible(int visible);
boolean isPaused(); boolean isPaused();
} }

View File

@ -1,202 +1,204 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="init">初始化中</string> <string name="init">初始化中</string>
<string name="please_wait">正在下载数据,请稍候</string> <string name="please_wait">正在下载数据,请稍候</string>
<string name="device_config">设备配置</string> <string name="device_config">设备配置</string>
<string name="device_change_cfg">更改设备配置</string> <string name="device_change_cfg">更改设备配置</string>
<string name="download_unneeded">没有需要下载的内容</string> <string name="download_unneeded">没有需要下载的内容</string>
<string name="connecting_to">正在连接到 %s</string> <string name="connecting_to">正在连接到 %s</string>
<string name="failed_connecting_to">连接到 %s 失败</string> <string name="failed_connecting_to">连接到 %s 失败</string>
<string name="error_connecting_to"> %s 连接出错</string> <string name="error_connecting_to">%s 连接出错</string>
<string name="dl_from">正在从 %s 下载数据</string> <string name="dl_from">正在从 %s 下载数据</string>
<string name="error_dl_from">从 %s 下载数据时出错</string> <string name="error_dl_from">从 %s 下载数据时出错</string>
<string name="error_write">写入到 %s 时出错</string> <string name="error_write">写入到 %s 时出错</string>
<string name="dl_progress">%1$.0f%% 已完成: 文件 %2$s</string> <string name="dl_progress">%1$.0f%% 已完成: 文件 %2$s</string>
<string name="dl_finished">已完成</string> <string name="dl_finished">已完成</string>
<string name="storage_phone">内部储存 - %d MB 空闲</string> <string name="storage_phone">内部储存 - %d MB 空闲</string>
<string name="storage_sd">SD卡储存 - %d MB 空闲</string> <string name="storage_sd">SD卡储存 - %d MB 空闲</string>
<string name="storage_custom">自定义目录</string> <string name="storage_custom">自定义目录</string>
<string name="storage_commandline">命令行参数,每行一个参数</string> <string name="storage_commandline">命令行参数,每行一个参数</string>
<string name="storage_question">数据文件安装位置</string> <string name="storage_question">数据文件安装位置</string>
<string name="optional_downloads">下载</string> <string name="optional_downloads">下载</string>
<string name="downloads">下载</string> <string name="downloads">下载</string>
<string name="ok">完成</string> <string name="ok">完成</string>
<string name="cancel">取消</string> <string name="cancel">取消</string>
<string name="controls_arrows">箭头 / 操纵杆 / 方向键</string> <string name="controls_arrows">箭头 / 操纵杆 / 方向键</string>
<string name="controls_trackball">轨迹球</string> <string name="controls_trackball">轨迹球</string>
<string name="controls_accel">加速度计</string> <string name="controls_accel">加速度计</string>
<string name="controls_touch">只使用触屏</string> <string name="controls_touch">只使用触屏</string>
<string name="controls_question">您的设备有哪些导航键?</string> <string name="controls_question">您的设备有哪些导航键?</string>
<string name="controls_additional">附加控制</string> <string name="controls_additional">附加控制</string>
<string name="controls_screenkb">屏幕键盘</string> <string name="controls_screenkb">屏幕键盘</string>
<string name="controls_accelnav">加速度计</string> <string name="controls_accelnav">加速度计</string>
<string name="controls_screenkb_size">屏幕键盘大小</string> <string name="controls_screenkb_size">屏幕键盘大小</string>
<string name="controls_screenkb_drawsize">按钮大小</string> <string name="controls_screenkb_drawsize">按钮大小</string>
<string name="controls_screenkb_large"></string> <string name="controls_screenkb_large"></string>
<string name="controls_screenkb_medium"></string> <string name="controls_screenkb_medium"></string>
<string name="controls_screenkb_small"></string> <string name="controls_screenkb_small"></string>
<string name="controls_screenkb_tiny">微小</string> <string name="controls_screenkb_tiny">微小</string>
<string name="controls_screenkb_custom">自定义</string> <string name="controls_screenkb_custom">自定义</string>
<string name="controls_screenkb_theme">屏幕键盘主题</string> <string name="controls_screenkb_theme">屏幕键盘主题</string>
<string name="controls_screenkb_by">%1$s by %2$s</string> <string name="controls_screenkb_by">%1$s by %2$s</string>
<string name="controls_screenkb_transparency">屏幕键盘透明度</string> <string name="controls_screenkb_transparency">屏幕键盘透明度</string>
<string name="controls_screenkb_trans_0">隐形</string> <string name="controls_screenkb_trans_0">隐形</string>
<string name="controls_screenkb_trans_1">半隐形</string> <string name="controls_screenkb_trans_1">半隐形</string>
<string name="controls_screenkb_trans_2">透明</string> <string name="controls_screenkb_trans_2">透明</string>
<string name="controls_screenkb_trans_3">半透明</string> <string name="controls_screenkb_trans_3">半透明</string>
<string name="controls_screenkb_trans_4">不透明</string> <string name="controls_screenkb_trans_4">不透明</string>
<string name="trackball_no_dampening">无阻碍</string> <string name="trackball_no_dampening">无阻碍</string>
<string name="trackball_fast"></string> <string name="trackball_fast"></string>
<string name="trackball_medium"></string> <string name="trackball_medium"></string>
<string name="trackball_slow"></string> <string name="trackball_slow"></string>
<string name="trackball_question">轨迹球阻碍</string> <string name="trackball_question">轨迹球阻碍</string>
<string name="accel_veryfast">非常快</string> <string name="accel_veryfast">非常快</string>
<string name="accel_fast"></string> <string name="accel_fast"></string>
<string name="accel_medium"></string> <string name="accel_medium"></string>
<string name="accel_slow"></string> <string name="accel_slow"></string>
<string name="accel_veryslow">非常慢</string> <string name="accel_veryslow">非常慢</string>
<string name="accel_question">加速度计灵敏度</string> <string name="accel_question">加速度计灵敏度</string>
<string name="accel_floating">Floating</string> <string name="accel_floating">Floating</string>
<string name="accel_fixed_start">在应用程序启动时修复</string> <string name="accel_fixed_start">在应用程序启动时修复</string>
<string name="accel_fixed_horiz">Fixed to table desk orientation</string> <string name="accel_fixed_horiz">Fixed to table desk orientation</string>
<string name="accel_question_center">加速度计中心位置</string> <string name="accel_question_center">加速度计中心位置</string>
<string name="mouse_emulation">鼠标模拟</string> <string name="mouse_emulation">鼠标模拟</string>
<string name="rightclick_question">单击鼠标右键</string> <string name="rightclick_question">单击鼠标右键</string>
<string name="rightclick_menu">菜单键</string> <string name="rightclick_menu">菜单键</string>
<string name="rightclick_key">物理按键</string> <string name="rightclick_key">物理按键</string>
<string name="rightclick_multitouch">双指触摸</string> <string name="rightclick_multitouch">双指触摸</string>
<string name="rightclick_pressure">使用按压力度</string> <string name="rightclick_pressure">使用按压力度</string>
<string name="rightclick_none">禁用鼠标右键</string> <string name="rightclick_none">禁用鼠标右键</string>
<string name="leftclick_question">鼠标左键单击</string> <string name="leftclick_question">鼠标左键单击</string>
<string name="leftclick_normal">正常</string> <string name="leftclick_normal">正常</string>
<string name="leftclick_near_cursor">触摸靠近鼠标光标</string> <string name="leftclick_near_cursor">触摸靠近鼠标光标</string>
<string name="leftclick_multitouch">双指触摸</string> <string name="leftclick_multitouch">双指触摸</string>
<string name="leftclick_pressure">使用按压力度</string> <string name="leftclick_pressure">使用按压力度</string>
<string name="leftclick_dpadcenter">轨迹球点击 / 操纵杆中心</string> <string name="leftclick_dpadcenter">轨迹球点击 / 操纵杆中心</string>
<string name="leftclick_timeout">长按一个点</string> <string name="leftclick_timeout">长按一个点</string>
<string name="leftclick_tap">点击</string> <string name="leftclick_tap">点击</string>
<string name="leftclick_tap_or_timeout">点击或长按</string> <string name="leftclick_tap_or_timeout">点击或长按</string>
<string name="leftclick_timeout_time">长按超时</string> <string name="leftclick_timeout_time">长按超时</string>
<string name="leftclick_timeout_time_0">0.3 秒</string> <string name="leftclick_timeout_time_0">0.3 秒</string>
<string name="leftclick_timeout_time_1">0.5 秒</string> <string name="leftclick_timeout_time_1">0.5 秒</string>
<string name="leftclick_timeout_time_2">0.7 秒</string> <string name="leftclick_timeout_time_2">0.7 秒</string>
<string name="leftclick_timeout_time_3">1 秒</string> <string name="leftclick_timeout_time_3">1 秒</string>
<string name="leftclick_timeout_time_4">1.5 秒</string> <string name="leftclick_timeout_time_4">1.5 秒</string>
<string name="click_with_dpadcenter">左键点击和轨迹球点击 / 操纵杆中心</string> <string name="click_with_dpadcenter">左键点击和轨迹球点击 / 操纵杆中心</string>
<string name="advanced">高级功能</string> <string name="advanced">高级功能</string>
<string name="mouse_keepaspectratio">保持4:3的屏幕宽高比</string> <string name="mouse_keepaspectratio">保持4:3的屏幕宽高比</string>
<string name="mouse_showcreenunderfinger">在单独的窗口中显示屏幕</string> <string name="mouse_showcreenunderfinger">在单独的窗口中显示屏幕</string>
<string name="mouse_showcreenunderfinger2">屏幕放大镜</string> <string name="mouse_showcreenunderfinger2">屏幕放大镜</string>
<string name="mouse_joystickmouse">使用操纵杆或轨迹球移动鼠标</string> <string name="mouse_joystickmouse">使用操纵杆或轨迹球移动鼠标</string>
<string name="mouse_joystickmousespeed">使用操纵杆移动鼠标时的速度</string> <string name="mouse_joystickmousespeed">使用操纵杆移动鼠标时的速度</string>
<string name="mouse_joystickmouseaccel">使用操纵杆加速移动鼠标</string> <string name="mouse_joystickmouseaccel">使用操纵杆加速移动鼠标</string>
<string name="mouse_relative">鼠标相对移动(笔记本模式)</string> <string name="mouse_relative">鼠标相对移动(笔记本模式)</string>
<string name="mouse_relative_speed">鼠标相对移动速度</string> <string name="mouse_relative_speed">鼠标相对移动速度</string>
<string name="mouse_relative_accel">鼠标相对移动加速</string> <string name="mouse_relative_accel">鼠标相对移动加速</string>
<string name="mouse_hover_jitter_filter">过滤指针/手指的抖动</string> <string name="mouse_hover_jitter_filter">过滤指针/手指的抖动</string>
<string name="mouse_gyroscope_mouse">用陀螺仪控制鼠标移动</string> <string name="mouse_gyroscope_mouse">用陀螺仪控制鼠标移动</string>
<string name="mouse_gyroscope_mouse_sensitivity">陀螺仪灵敏度</string> <string name="mouse_gyroscope_mouse_sensitivity">陀螺仪灵敏度</string>
<string name="mouse_finger_hover">手指抖动</string> <string name="mouse_finger_hover">手指抖动</string>
<string name="mouse_subframe_touch_events">每一帧的多点触摸事件</string> <string name="mouse_subframe_touch_events">每一帧的多点触摸事件</string>
<string name="none"></string> <string name="none"></string>
<string name="measurepressure">校准触摸屏压力</string> <string name="measurepressure">校准触摸屏压力</string>
<string name="measurepressure_touchplease">请将手指滑过屏幕两秒钟</string> <string name="measurepressure_touchplease">请将手指滑过屏幕两秒钟</string>
<string name="measurepressure_response">压力 %1$03d 半径 %2$03d</string> <string name="measurepressure_response">压力 %1$03d 半径 %2$03d</string>
<string name="audiobuf_verysmall">非常小(较新的设备,延迟低)</string> <string name="audiobuf_verysmall">非常小(较新的设备,延迟低)</string>
<string name="audiobuf_small"></string> <string name="audiobuf_small"></string>
<string name="audiobuf_medium">中等</string> <string name="audiobuf_medium">中等</string>
<string name="audiobuf_large">大(较老的设备,如果声音不稳定请选此项)</string> <string name="audiobuf_large">大(较老的设备,如果声音不稳定请选此项)</string>
<string name="audiobuf_question">音频缓冲大小</string> <string name="audiobuf_question">音频缓冲大小</string>
<string name="remap_hwkeys">映射物理按键</string> <string name="remap_hwkeys">映射物理按键</string>
<string name="remap_hwkeys_press">按下任意按键 除了HOME键和POWER键如音量键</string> <string name="remap_hwkeys_press">按下任意按键 除了HOME键和POWER键如音量键</string>
<string name="remap_hwkeys_select">选择SDL按键</string> <string name="remap_hwkeys_select">选择SDL按键</string>
<string name="remap_hwkeys_select_simple">选择动作</string> <string name="remap_hwkeys_select_simple">选择动作</string>
<string name="remap_hwkeys_select_more_keys">显示所有按键</string> <string name="remap_hwkeys_select_more_keys">显示所有按键</string>
<string name="remap_screenkb">映射屏幕控件</string> <string name="remap_screenkb">映射屏幕控件</string>
<string name="remap_screenkb_joystick">手柄</string> <string name="remap_screenkb_joystick">手柄</string>
<string name="remap_screenkb_button">按钮</string> <string name="remap_screenkb_button">按钮</string>
<string name="remap_screenkb_button_text">文本输入按钮</string> <string name="remap_screenkb_button_text">文本输入按钮</string>
<string name="remap_screenkb_button_gestures">双指手势</string> <string name="remap_screenkb_button_gestures">双指手势</string>
<string name="remap_screenkb_button_gestures_sensitivity">双指手势灵敏度</string> <string name="remap_screenkb_button_gestures_sensitivity">双指手势灵敏度</string>
<string name="remap_screenkb_button_zoomin">双指放大</string> <string name="remap_screenkb_button_zoomin">双指放大</string>
<string name="remap_screenkb_button_zoomout">双指缩小</string> <string name="remap_screenkb_button_zoomout">双指缩小</string>
<string name="remap_screenkb_button_rotateleft">双指向左旋转</string> <string name="remap_screenkb_button_rotateleft">双指向左旋转</string>
<string name="remap_screenkb_button_rotateright">双指向右旋转</string> <string name="remap_screenkb_button_rotateright">双指向右旋转</string>
<string name="screenkb_custom_layout">自定义屏幕键盘布局</string> <string name="screenkb_custom_layout">自定义屏幕键盘布局</string>
<string name="screenkb_custom_layout_help">按返回键结束,在空白区域滑动调整按钮大小</string> <string name="screenkb_custom_layout_help">按返回键结束,在空白区域滑动调整按钮大小</string>
<string name="screenkb_floating_joystick">浮动操纵杆</string> <string name="screenkb_floating_joystick">浮动操纵杆</string>
<string name="calibrate_touchscreen">校准触摸屏</string> <string name="calibrate_touchscreen">校准触摸屏</string>
<string name="calibrate_touchscreen_touch">触摸屏幕的所有边缘,按返回键结束</string> <string name="calibrate_touchscreen_touch">触摸屏幕的所有边缘,按返回键结束</string>
<string name="video">视频选项</string> <string name="video">视频选项</string>
<string name="video_smooth">线性过滤</string> <string name="video_smooth">线性过滤</string>
<string name="video_separatethread">用单线程处理视频可能会提高FPS也可能使程序崩溃</string> <string name="video_separatethread">用单线程处理视频可能会提高FPS也可能使程序崩溃</string>
<string name="video_orientation_vertical">切换横屏/竖屏</string> <string name="video_orientation_vertical">切换横屏/竖屏</string>
<string name="video_orientation_autodetect">自动检测屏幕方向</string> <string name="video_orientation_autodetect">自动检测屏幕方向</string>
<string name="video_bpp_24">24 bpp颜色深度</string> <string name="video_bpp_24">24 bpp颜色深度</string>
<string name="video_immersive">隐藏系统导航按钮/沉浸模式</string> <string name="video_immersive">隐藏系统导航按钮/沉浸模式</string>
<string name="tv_borders">电视边框</string> <string name="tv_borders">电视边框</string>
<string name="text_edit_click_here">点击开始输入,按返回键结束</string> <string name="text_edit_click_here">点击开始输入,按返回键结束</string>
<string name="display_size_mouse">鼠标仿真模式</string> <string name="display_size_mouse">鼠标仿真模式</string>
<string name="display_size">显示仿真鼠标的大小</string> <string name="display_size">显示仿真鼠标的大小</string>
<string name="display_size_desktop">桌面版,无仿真</string> <string name="display_size_desktop">桌面版,无仿真</string>
<string name="display_size_large">大(适用于平板电脑)</string> <string name="display_size_large">大(适用于平板电脑)</string>
<string name="display_size_small">小,放大镜</string> <string name="display_size_small">小,放大镜</string>
<string name="display_size_small_touchpad">小,触摸模式</string> <string name="display_size_small_touchpad">小,触摸模式</string>
<string name="display_size_tiny">很小</string> <string name="display_size_tiny">很小</string>
<string name="display_size_tiny_touchpad">很小,触摸模式</string> <string name="display_size_tiny_touchpad">很小,触摸模式</string>
<string name="show_more_options">显示更多选项</string> <string name="show_more_options">显示更多选项</string>
<string name="hardware_mouse_detected">检测到鼠标硬件,禁用鼠标仿真</string> <string name="hardware_mouse_detected">检测到鼠标硬件,禁用鼠标仿真</string>
<string name="not_enough_ram">没有足够的 RAM</string> <string name="not_enough_ram">没有足够的 RAM</string>
<string name="not_enough_ram_size">本程序需要 %1$d Mb 的RAM您的设备有 %2$d Mb</string> <string name="not_enough_ram_size">本程序需要 %1$d Mb 的RAM您的设备有 %2$d Mb</string>
<string name="ignore">忽略</string> <string name="ignore">忽略</string>
<string name="calibrate_gyroscope">校准陀螺仪</string> <string name="calibrate_gyroscope">校准陀螺仪</string>
<string name="calibrate_gyroscope_text">将您的设备放在水平表面上</string> <string name="calibrate_gyroscope_text">将您的设备放在水平表面上</string>
<string name="calibrate_gyroscope_not_supported">您的设备没有陀螺仪</string> <string name="calibrate_gyroscope_not_supported">您的设备没有陀螺仪</string>
<string name="reset_config">将所有配置重置为默认值</string> <string name="reset_config">将所有配置重置为默认值</string>
<string name="reset_config_ask">是否将所有选项重置为默认值?</string> <string name="reset_config_ask">是否将所有选项重置为默认值?</string>
<string name="cancel_download">是否取消数据下载?</string> <string name="cancel_download">是否取消数据下载?</string>
<string name="cancel_download_resume">您可以稍后恢复它,数据不会被下载两次。</string> <string name="cancel_download_resume">您可以稍后恢复它,数据不会被下载两次。</string>
<string name="yes"></string> <string name="yes"></string>
<string name="no"></string> <string name="no"></string>
<!-- Play Game Services strings --> <!-- Play Game Services strings -->
<string name="gamehelper_sign_in_failed">无法登录,请检查您的网络连接,然后重试。</string> <string name="gamehelper_sign_in_failed">无法登录,请检查您的网络连接,然后重试。</string>
<string name="gamehelper_app_misconfigured">应用程序配置不正确。请检查包名和签名证书是否与开发者控制台中创建的客户端ID一致。此外如果应用程序尚未发布请检查您的帐户是否为测试人员帐户。详细信息请参阅日志。</string> <string name="gamehelper_app_misconfigured">
<string name="gamehelper_license_failed">许可证检查失败。</string> 应用程序配置不正确。请检查包名和签名证书是否与开发者控制台中创建的客户端ID一致。此外如果应用程序尚未发布请检查您的帐户是否为测试人员帐户。详细信息请参阅日志。
<string name="gamehelper_unknown_error">未知错误。</string> </string>
<string name="accessing_network">正在访问网络,请稍候</string> <string name="gamehelper_license_failed">许可证检查失败。</string>
<string name="gamehelper_unknown_error">未知错误。</string>
<string name="accessing_network">正在访问网络,请稍候</string>
<string name="restarting_please_wait">重新启动中,请稍候。</string> <string name="restarting_please_wait">重新启动中,请稍候。</string>
<string name="notification_app_is_running">%s 正在运行中</string> <string name="notification_app_is_running">%s 正在运行中</string>
<string name="notification_stop">停止</string> <string name="notification_stop">停止</string>
</resources> </resources>

View File

@ -1,4 +1,4 @@
<resources> <resources>
<dimen name="screen_border_horizontal">0dp</dimen> <dimen name="screen_border_horizontal">0dp</dimen>
<dimen name="screen_border_vertical">0dp</dimen> <dimen name="screen_border_vertical">0dp</dimen>
</resources> </resources>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<item name="left" type="id" /> <item name="left" type="id"/>
<item name="right" type="id" /> <item name="right" type="id"/>
<item name="top" type="id" /> <item name="top" type="id"/>
<item name="bottom" type="id" /> <item name="bottom" type="id"/>
</resources> </resources>

View File

@ -2,205 +2,211 @@
<resources <resources
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingTranslation"> tools:ignore="MissingTranslation">
<string name="init">Initializing</string> <string name="init">Initializing</string>
<string name="please_wait">Please wait while data is being downloaded</string> <string name="please_wait">Please wait while data is being downloaded</string>
<string name="device_config">Device configuration</string> <string name="device_config">Device configuration</string>
<string name="device_change_cfg">Change device configuration</string> <string name="device_change_cfg">Change device configuration</string>
<string name="download_unneeded">No need to download</string> <string name="download_unneeded">No need to download</string>
<string name="connecting_to">Connecting to %s</string> <string name="connecting_to">Connecting to %s</string>
<string name="failed_connecting_to">Failed connecting to %s</string> <string name="failed_connecting_to">Failed connecting to %s</string>
<string name="error_connecting_to">Error connecting to %s</string> <string name="error_connecting_to">Error connecting to %s</string>
<string name="dl_from">Downloading data from %s</string> <string name="dl_from">Downloading data from %s</string>
<string name="error_dl_from">Error downloading data from %s</string> <string name="error_dl_from">Error downloading data from %s</string>
<string name="error_write">Error writing to %s</string> <string name="error_write">Error writing to %s</string>
<string name="dl_progress">%1$.0f%% done: file %2$s</string> <string name="dl_progress">%1$.0f%% done: file %2$s</string>
<string name="dl_finished">Finished</string> <string name="dl_finished">Finished</string>
<string name="storage_phone">Internal storage - %d MB free</string> <string name="storage_phone">Internal storage - %d MB free</string>
<string name="storage_sd">SD card storage - %d MB free</string> <string name="storage_sd">SD card storage - %d MB free</string>
<string name="storage_custom">Specify directory</string> <string name="storage_custom">Specify directory</string>
<string name="storage_commandline">Command line parameters, one argument per line</string> <string name="storage_commandline">Command line parameters, one argument per line</string>
<string name="storage_question">Data installation location</string> <string name="storage_question">Data installation location</string>
<string name="optional_downloads">Downloads</string> <string name="optional_downloads">Downloads</string>
<string name="downloads">Downloads</string> <string name="downloads">Downloads</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="controls_arrows">Arrows / joystick / dpad</string> <string name="controls_arrows">Arrows / joystick / dpad</string>
<string name="controls_trackball">Trackball</string> <string name="controls_trackball">Trackball</string>
<string name="controls_accel">Accelerometer</string> <string name="controls_accel">Accelerometer</string>
<string name="controls_touch">Touchscreen only</string> <string name="controls_touch">Touchscreen only</string>
<string name="controls_question">What kind of navigation keys does your device have?</string> <string name="controls_question">What kind of navigation keys does your device have?</string>
<string name="controls_additional">Additional controls</string> <string name="controls_additional">Additional controls</string>
<string name="controls_screenkb">On-screen screenKeyboard</string> <string name="controls_screenkb">On-screen screenKeyboard</string>
<string name="controls_accelnav">Accelerometer</string> <string name="controls_accelnav">Accelerometer</string>
<string name="controls_screenkb_size">On-screen screenKeyboard size</string> <string name="controls_screenkb_size">On-screen screenKeyboard size</string>
<string name="controls_screenkb_drawsize">Size of button images</string> <string name="controls_screenkb_drawsize">Size of button images</string>
<string name="controls_screenkb_large">Large</string> <string name="controls_screenkb_large">Large</string>
<string name="controls_screenkb_medium">Medium</string> <string name="controls_screenkb_medium">Medium</string>
<string name="controls_screenkb_small">Small</string> <string name="controls_screenkb_small">Small</string>
<string name="controls_screenkb_tiny">Tiny</string> <string name="controls_screenkb_tiny">Tiny</string>
<string name="controls_screenkb_custom">Custom</string> <string name="controls_screenkb_custom">Custom</string>
<string name="controls_screenkb_theme">On-screen screenKeyboard theme</string> <string name="controls_screenkb_theme">On-screen screenKeyboard theme</string>
<string name="controls_screenkb_by">%1$s by %2$s</string> <string name="controls_screenkb_by">%1$s by %2$s</string>
<string name="controls_screenkb_transparency">On-screen screenKeyboard transparency</string> <string name="controls_screenkb_transparency">On-screen screenKeyboard transparency</string>
<string name="controls_screenkb_trans_0">Invisible</string> <string name="controls_screenkb_trans_0">Invisible</string>
<string name="controls_screenkb_trans_1">Almost invisible</string> <string name="controls_screenkb_trans_1">Almost invisible</string>
<string name="controls_screenkb_trans_2">Transparent</string> <string name="controls_screenkb_trans_2">Transparent</string>
<string name="controls_screenkb_trans_3">Semi-transparent</string> <string name="controls_screenkb_trans_3">Semi-transparent</string>
<string name="controls_screenkb_trans_4">Non-transparent</string> <string name="controls_screenkb_trans_4">Non-transparent</string>
<string name="trackball_no_dampening">No dampening</string> <string name="trackball_no_dampening">No dampening</string>
<string name="trackball_fast">Fast</string> <string name="trackball_fast">Fast</string>
<string name="trackball_medium">Medium</string> <string name="trackball_medium">Medium</string>
<string name="trackball_slow">Slow</string> <string name="trackball_slow">Slow</string>
<string name="trackball_question">Trackball dampening</string> <string name="trackball_question">Trackball dampening</string>
<string name="accel_veryfast">Very fast</string> <string name="accel_veryfast">Very fast</string>
<string name="accel_fast">Fast</string> <string name="accel_fast">Fast</string>
<string name="accel_medium">Medium</string> <string name="accel_medium">Medium</string>
<string name="accel_slow">Slow</string> <string name="accel_slow">Slow</string>
<string name="accel_veryslow">Very slow</string> <string name="accel_veryslow">Very slow</string>
<string name="accel_question">Accelerometer sensitivity</string> <string name="accel_question">Accelerometer sensitivity</string>
<string name="accel_floating">Floating</string> <string name="accel_floating">Floating</string>
<string name="accel_fixed_start">Fixed when application starts</string> <string name="accel_fixed_start">Fixed when application starts</string>
<string name="accel_fixed_horiz">Fixed to table desk orientation</string> <string name="accel_fixed_horiz">Fixed to table desk orientation</string>
<string name="accel_question_center">Accelerometer center position</string> <string name="accel_question_center">Accelerometer center position</string>
<string name="mouse_emulation">Mouse emulation</string> <string name="mouse_emulation">Mouse emulation</string>
<string name="rightclick_question">Right mouse click</string> <string name="rightclick_question">Right mouse click</string>
<string name="rightclick_menu">Menu key</string> <string name="rightclick_menu">Menu key</string>
<string name="rightclick_key">Physical key</string> <string name="rightclick_key">Physical key</string>
<string name="rightclick_multitouch">Touch screen with second finger</string> <string name="rightclick_multitouch">Touch screen with second finger</string>
<string name="rightclick_pressure">Touch screen with force</string> <string name="rightclick_pressure">Touch screen with force</string>
<string name="rightclick_none">Disable right mouse click</string> <string name="rightclick_none">Disable right mouse click</string>
<string name="leftclick_question">Left mouse click</string> <string name="leftclick_question">Left mouse click</string>
<string name="leftclick_normal">Normal</string> <string name="leftclick_normal">Normal</string>
<string name="leftclick_near_cursor">Touch near mouse cursor</string> <string name="leftclick_near_cursor">Touch near mouse cursor</string>
<string name="leftclick_multitouch">Touch screen with second finger</string> <string name="leftclick_multitouch">Touch screen with second finger</string>
<string name="leftclick_pressure">Touch screen with force</string> <string name="leftclick_pressure">Touch screen with force</string>
<string name="leftclick_dpadcenter">Trackball click / joystick center</string> <string name="leftclick_dpadcenter">Trackball click / joystick center</string>
<string name="leftclick_timeout">Hold at the same spot</string> <string name="leftclick_timeout">Hold at the same spot</string>
<string name="leftclick_tap">Tap</string> <string name="leftclick_tap">Tap</string>
<string name="leftclick_tap_or_timeout">Tap or hold</string> <string name="leftclick_tap_or_timeout">Tap or hold</string>
<string name="leftclick_timeout_time">Holding timeout</string> <string name="leftclick_timeout_time">Holding timeout</string>
<string name="leftclick_timeout_time_0">0.3 sec</string> <string name="leftclick_timeout_time_0">0.3 sec</string>
<string name="leftclick_timeout_time_1">0.5 sec</string> <string name="leftclick_timeout_time_1">0.5 sec</string>
<string name="leftclick_timeout_time_2">0.7 sec</string> <string name="leftclick_timeout_time_2">0.7 sec</string>
<string name="leftclick_timeout_time_3">1 sec</string> <string name="leftclick_timeout_time_3">1 sec</string>
<string name="leftclick_timeout_time_4">1.5 sec</string> <string name="leftclick_timeout_time_4">1.5 sec</string>
<string name="click_with_dpadcenter">Left mouse click with trackball / joystick center</string> <string name="click_with_dpadcenter">Left mouse click with trackball / joystick center</string>
<string name="advanced">Advanced features</string> <string name="advanced">Advanced features</string>
<string name="mouse_keepaspectratio">Keep 4:3 screen aspect ratio</string> <string name="mouse_keepaspectratio">Keep 4:3 screen aspect ratio</string>
<string name="mouse_showcreenunderfinger">Show screen under finger in separate window</string> <string name="mouse_showcreenunderfinger">Show screen under finger in separate window</string>
<string name="mouse_showcreenunderfinger2">On-screen magnifying glass</string> <string name="mouse_showcreenunderfinger2">On-screen magnifying glass</string>
<string name="mouse_joystickmouse">Move mouse with joystick or trackball</string> <string name="mouse_joystickmouse">Move mouse with joystick or trackball</string>
<string name="mouse_joystickmousespeed">Move mouse with joystick speed</string> <string name="mouse_joystickmousespeed">Move mouse with joystick speed</string>
<string name="mouse_joystickmouseaccel">Move mouse with joystick acceleration</string> <string name="mouse_joystickmouseaccel">Move mouse with joystick acceleration</string>
<string name="mouse_relative">Relative mouse movement (laptop mode)</string> <string name="mouse_relative">Relative mouse movement (laptop mode)</string>
<string name="mouse_relative_speed">Relative mouse movement speed</string> <string name="mouse_relative_speed">Relative mouse movement speed</string>
<string name="mouse_relative_accel">Relative mouse movement acceleration</string> <string name="mouse_relative_accel">Relative mouse movement acceleration</string>
<string name="mouse_hover_jitter_filter">Filter jitter for stylus/finger hover</string> <string name="mouse_hover_jitter_filter">Filter jitter for stylus/finger hover</string>
<string name="mouse_gyroscope_mouse">Control mouse with gyroscope</string> <string name="mouse_gyroscope_mouse">Control mouse with gyroscope</string>
<string name="mouse_gyroscope_mouse_sensitivity">Gyroscope sensitivity</string> <string name="mouse_gyroscope_mouse_sensitivity">Gyroscope sensitivity</string>
<string name="mouse_finger_hover">Finger hover</string> <string name="mouse_finger_hover">Finger hover</string>
<string name="mouse_subframe_touch_events">Multiple touch events per video frame</string> <string name="mouse_subframe_touch_events">Multiple touch events per video frame</string>
<string name="none">None</string> <string name="none">None</string>
<string name="measurepressure">Calibrate touchscreen pressure</string> <string name="measurepressure">Calibrate touchscreen pressure</string>
<string name="measurepressure_touchplease">Please slide finger across the screen for two seconds</string> <string name="measurepressure_touchplease">Please slide finger across the screen for two seconds</string>
<string name="measurepressure_response">Pressure %1$03d radius %2$03d</string> <string name="measurepressure_response">Pressure %1$03d radius %2$03d</string>
<string name="audiobuf_verysmall">Very small (fast devices, less lag)</string> <string name="audiobuf_verysmall">Very small (fast devices, less lag)</string>
<string name="audiobuf_small">Small</string> <string name="audiobuf_small">Small</string>
<string name="audiobuf_medium">Medium</string> <string name="audiobuf_medium">Medium</string>
<string name="audiobuf_large">Large (older devices, if sound is choppy)</string> <string name="audiobuf_large">Large (older devices, if sound is choppy)</string>
<string name="audiobuf_question">Size of audio buffer</string> <string name="audiobuf_question">Size of audio buffer</string>
<string name="remap_hwkeys">Remap physical keys</string> <string name="remap_hwkeys">Remap physical keys</string>
<string name="remap_hwkeys_press">Press any key except HOME and POWER, you may use volume keys</string> <string name="remap_hwkeys_press">Press any key except HOME and POWER, you may use volume keys</string>
<string name="remap_hwkeys_select">Select SDL keycode</string> <string name="remap_hwkeys_select">Select SDL keycode</string>
<string name="remap_hwkeys_select_simple">Select action</string> <string name="remap_hwkeys_select_simple">Select action</string>
<string name="remap_hwkeys_select_more_keys">Show all keycodes</string> <string name="remap_hwkeys_select_more_keys">Show all keycodes</string>
<string name="remap_screenkb">Remap on-screen controls</string> <string name="remap_screenkb">Remap on-screen controls</string>
<string name="remap_screenkb_joystick">Joystick</string> <string name="remap_screenkb_joystick">Joystick</string>
<string name="remap_screenkb_button">Button</string> <string name="remap_screenkb_button">Button</string>
<string name="remap_screenkb_button_text">Text input button</string> <string name="remap_screenkb_button_text">Text input button</string>
<string name="remap_screenkb_button_gestures">Two-finger screen gestures</string> <string name="remap_screenkb_button_gestures">Two-finger screen gestures</string>
<string name="remap_screenkb_button_gestures_sensitivity">Two-finger screen gestures sensitivity</string> <string name="remap_screenkb_button_gestures_sensitivity">Two-finger screen gestures sensitivity</string>
<string name="remap_screenkb_button_zoomin">Zoom in two-finger gesture</string> <string name="remap_screenkb_button_zoomin">Zoom in two-finger gesture</string>
<string name="remap_screenkb_button_zoomout">Zoom out two-finger gesture</string> <string name="remap_screenkb_button_zoomout">Zoom out two-finger gesture</string>
<string name="remap_screenkb_button_rotateleft">Rotate left two-finger gesture</string> <string name="remap_screenkb_button_rotateleft">Rotate left two-finger gesture</string>
<string name="remap_screenkb_button_rotateright">Rotate right two-finger gesture</string> <string name="remap_screenkb_button_rotateright">Rotate right two-finger gesture</string>
<string name="screenkb_custom_layout">Customize on-screen screenKeyboard layout</string> <string name="screenkb_custom_layout">Customize on-screen screenKeyboard layout</string>
<string name="screenkb_custom_layout_help">Press BACK when done. Resize buttons by sliding on empty space.</string> <string name="screenkb_custom_layout_help">Press BACK when done. Resize buttons by sliding on empty space.</string>
<string name="screenkb_floating_joystick">Floating joystick</string> <string name="screenkb_floating_joystick">Floating joystick</string>
<string name="calibrate_touchscreen">Calibrate touchscreen</string> <string name="calibrate_touchscreen">Calibrate touchscreen</string>
<string name="calibrate_touchscreen_touch">Touch all edges of the screen, press BACK when done</string> <string name="calibrate_touchscreen_touch">Touch all edges of the screen, press BACK when done</string>
<string name="video">Video settings</string> <string name="video">Video settings</string>
<string name="video_smooth">Linear video filtering</string> <string name="video_smooth">Linear video filtering</string>
<string name="video_separatethread">Separate thread for video, it can increase FPS, it also can crash the app</string> <string name="video_separatethread">Separate thread for video, it can increase FPS, it also can crash the app
<string name="video_orientation_vertical">Portrait/vertical screen orientation</string> </string>
<string name="video_orientation_autodetect">Auto-detect screen orientation</string> <string name="video_orientation_vertical">Portrait/vertical screen orientation</string>
<string name="video_bpp_24">24 bpp screen color depth</string> <string name="video_orientation_autodetect">Auto-detect screen orientation</string>
<string name="video_immersive">Hide system navigation buttons / immersive mode</string> <string name="video_bpp_24">24 bpp screen color depth</string>
<string name="tv_borders">TV borders</string> <string name="video_immersive">Hide system navigation buttons / immersive mode</string>
<string name="tv_borders">TV borders</string>
<string name="text_edit_click_here">Tap to start typing, press Back when done</string> <string name="text_edit_click_here">Tap to start typing, press Back when done</string>
<string name="display_size_mouse">Mouse emulation mode</string> <string name="display_size_mouse">Mouse emulation mode</string>
<string name="display_size">Display size for mouse emulation</string> <string name="display_size">Display size for mouse emulation</string>
<string name="display_size_desktop">Desktop, no emulation</string> <string name="display_size_desktop">Desktop, no emulation</string>
<string name="display_size_large">Large (tablets)</string> <string name="display_size_large">Large (tablets)</string>
<string name="display_size_small">Small, magnifying glass</string> <string name="display_size_small">Small, magnifying glass</string>
<string name="display_size_small_touchpad">Small, touchpad mode</string> <string name="display_size_small_touchpad">Small, touchpad mode</string>
<string name="display_size_tiny">Tiny</string> <string name="display_size_tiny">Tiny</string>
<string name="display_size_tiny_touchpad">Tiny, touchpad mode</string> <string name="display_size_tiny_touchpad">Tiny, touchpad mode</string>
<string name="show_more_options">Show more options</string> <string name="show_more_options">Show more options</string>
<string name="hardware_mouse_detected">Hardware mouse detected, disabling mouse emulation</string> <string name="hardware_mouse_detected">Hardware mouse detected, disabling mouse emulation</string>
<string name="not_enough_ram">Not enough RAM</string> <string name="not_enough_ram">Not enough RAM</string>
<string name="not_enough_ram_size">This app needs %1$d Mb RAM, your device has %2$d Mb</string> <string name="not_enough_ram_size">This app needs %1$d Mb RAM, your device has %2$d Mb</string>
<string name="ignore">Ignore</string> <string name="ignore">Ignore</string>
<string name="calibrate_gyroscope">Calibrate gyroscope</string> <string name="calibrate_gyroscope">Calibrate gyroscope</string>
<string name="calibrate_gyroscope_text">Put your device on a flat surface</string> <string name="calibrate_gyroscope_text">Put your device on a flat surface</string>
<string name="calibrate_gyroscope_not_supported">Your device does not have gyroscope</string> <string name="calibrate_gyroscope_not_supported">Your device does not have gyroscope</string>
<string name="reset_config">Reset config to defaults</string> <string name="reset_config">Reset config to defaults</string>
<string name="reset_config_ask">Reset all options to default values?</string> <string name="reset_config_ask">Reset all options to default values?</string>
<string name="cancel_download">Cancel data downloading?</string> <string name="cancel_download">Cancel data downloading?</string>
<string name="cancel_download_resume">You can resume it later, the data will not be downloaded twice.</string> <string name="cancel_download_resume">You can resume it later, the data will not be downloaded twice.</string>
<string name="yes">Yes</string> <string name="yes">Yes</string>
<string name="no">No</string> <string name="no">No</string>
<!-- Play Game Services strings --> <!-- Play Game Services strings -->
<string name="gamehelper_sign_in_failed">Failed to sign in. Please check your network connection and try again.</string> <string name="gamehelper_sign_in_failed">Failed to sign in. Please check your network connection and try again.
<string name="gamehelper_app_misconfigured">The application is incorrectly configured. Check that the package name and signing certificate match the client ID created in Developer Console. Also, if the application is not yet published, check that the account you are trying to sign in with is listed as a tester account. See logs for more information.</string> </string>
<string name="gamehelper_license_failed">License check failed.</string> <string name="gamehelper_app_misconfigured">The application is incorrectly configured. Check that the package name
<string name="gamehelper_unknown_error">Unknown error.</string> and signing certificate match the client ID created in Developer Console. Also, if the application is not yet
<string name="accessing_network">Accessing network, please wait</string> published, check that the account you are trying to sign in with is listed as a tester account. See logs for
more information.
</string>
<string name="gamehelper_license_failed">License check failed.</string>
<string name="gamehelper_unknown_error">Unknown error.</string>
<string name="accessing_network">Accessing network, please wait</string>
<string name="google_play_game_services_app_id" translatable="false">==GOOGLEPLAYGAMESERVICES_APP_ID==</string> <string name="google_play_game_services_app_id" translatable="false">==GOOGLEPLAYGAMESERVICES_APP_ID==</string>
<string name="restarting_please_wait">Restarting, please wait.</string> <string name="restarting_please_wait">Restarting, please wait.</string>
<string name="notification_app_is_running">%s is running</string> <string name="notification_app_is_running">%s is running</string>
<string name="notification_stop">Stop</string> <string name="notification_stop">Stop</string>
</resources> </resources>

View File

@ -1,49 +1,50 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Keyboard android:keyWidth="10.000002%p" android:keyHeight="10.000002%p" android:horizontalGap="0.0px" android:verticalGap="0.0px" <Keyboard android:keyWidth="10.000002%p" android:keyHeight="10.000002%p" android:horizontalGap="0.0px"
xmlns:android="http://schemas.android.com/apk/res/android"> android:verticalGap="0.0px"
<Row> xmlns:android="http://schemas.android.com/apk/res/android">
<Key android:codes="45" android:keyEdgeFlags="left" android:keyLabel="q" /> <Row>
<Key android:codes="51" android:keyLabel="w" /> <Key android:codes="45" android:keyEdgeFlags="left" android:keyLabel="q"/>
<Key android:codes="33" android:keyLabel="e" /> <Key android:codes="51" android:keyLabel="w"/>
<Key android:codes="46" android:keyLabel="r" /> <Key android:codes="33" android:keyLabel="e"/>
<Key android:codes="48" android:keyLabel="t" /> <Key android:codes="46" android:keyLabel="r"/>
<Key android:codes="53" android:keyLabel="y" /> <Key android:codes="48" android:keyLabel="t"/>
<Key android:codes="49" android:keyLabel="u" /> <Key android:codes="53" android:keyLabel="y"/>
<Key android:codes="37" android:keyLabel="i" /> <Key android:codes="49" android:keyLabel="u"/>
<Key android:codes="43" android:keyLabel="o" /> <Key android:codes="37" android:keyLabel="i"/>
<Key android:codes="44" android:keyEdgeFlags="right" android:keyLabel="p" /> <Key android:codes="43" android:keyLabel="o"/>
</Row> <Key android:codes="44" android:keyEdgeFlags="right" android:keyLabel="p"/>
<Row> </Row>
<Key android:codes="29" android:keyEdgeFlags="left" android:keyLabel="a" /> <Row>
<Key android:codes="47" android:keyLabel="s" /> <Key android:codes="29" android:keyEdgeFlags="left" android:keyLabel="a"/>
<Key android:codes="32" android:keyLabel="d" /> <Key android:codes="47" android:keyLabel="s"/>
<Key android:codes="34" android:keyLabel="f" /> <Key android:codes="32" android:keyLabel="d"/>
<Key android:codes="35" android:keyLabel="g" /> <Key android:codes="34" android:keyLabel="f"/>
<Key android:codes="36" android:keyLabel="h" /> <Key android:codes="35" android:keyLabel="g"/>
<Key android:codes="38" android:keyLabel="j" /> <Key android:codes="36" android:keyLabel="h"/>
<Key android:codes="39" android:keyLabel="k" /> <Key android:codes="38" android:keyLabel="j"/>
<Key android:codes="40" android:keyLabel="l" /> <Key android:codes="39" android:keyLabel="k"/>
<Key android:codes="74" android:keyEdgeFlags="right" android:keyLabel=";" /> <Key android:codes="40" android:keyLabel="l"/>
</Row> <Key android:codes="74" android:keyEdgeFlags="right" android:keyLabel=";"/>
<Row> </Row>
<Key android:codes="-1" android:keyEdgeFlags="left" android:keyLabel="⇪" /> <Row>
<Key android:codes="54" android:keyLabel="z" /> <Key android:codes="-1" android:keyEdgeFlags="left" android:keyLabel="⇪"/>
<Key android:codes="52" android:keyLabel="x" /> <Key android:codes="54" android:keyLabel="z"/>
<Key android:codes="31" android:keyLabel="c" /> <Key android:codes="52" android:keyLabel="x"/>
<Key android:codes="50" android:keyLabel="v" /> <Key android:codes="31" android:keyLabel="c"/>
<Key android:codes="30" android:keyLabel="b" /> <Key android:codes="50" android:keyLabel="v"/>
<Key android:codes="42" android:keyLabel="n" /> <Key android:codes="30" android:keyLabel="b"/>
<Key android:codes="41" android:keyLabel="m" /> <Key android:codes="42" android:keyLabel="n"/>
<Key android:keyWidth="20.000004%p" android:codes="67" android:keyEdgeFlags="right" android:keyLabel="≪ ×" /> <Key android:codes="41" android:keyLabel="m"/>
</Row> <Key android:keyWidth="20.000004%p" android:codes="67" android:keyEdgeFlags="right" android:keyLabel="≪ ×"/>
<Row android:rowEdgeFlags="bottom"> </Row>
<Key android:codes="-6" android:keyEdgeFlags="left" android:keyLabel="123…" /> <Row android:rowEdgeFlags="bottom">
<Key android:codes="71" android:keyLabel="[" /> <Key android:codes="-6" android:keyEdgeFlags="left" android:keyLabel="123…"/>
<Key android:codes="72" android:keyLabel="]" /> <Key android:codes="71" android:keyLabel="["/>
<Key android:codes="76" android:keyLabel="/" /> <Key android:codes="72" android:keyLabel="]"/>
<Key android:keyWidth="20.000004%p" android:codes="62" android:keyLabel="Space" /> <Key android:codes="76" android:keyLabel="/"/>
<Key android:codes="55" android:keyLabel="," /> <Key android:keyWidth="20.000004%p" android:codes="62" android:keyLabel="Space"/>
<Key android:codes="56" android:keyLabel="." /> <Key android:codes="55" android:keyLabel=","/>
<Key android:keyWidth="20.000004%p" android:codes="66" android:keyEdgeFlags="right" android:keyLabel="Enter" /> <Key android:codes="56" android:keyLabel="."/>
</Row> <Key android:keyWidth="20.000004%p" android:codes="66" android:keyEdgeFlags="right" android:keyLabel="Enter"/>
</Row>
</Keyboard> </Keyboard>

View File

@ -1,57 +1,57 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java --> <!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" <Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p" android:keyWidth="10%p"
android:horizontalGap="0px" android:horizontalGap="0px"
android:verticalGap="0px" android:verticalGap="0px"
android:keyHeight="10%p"> android:keyHeight="10%p">
<Row> <Row>
<Key android:codes="8" android:keyLabel="1" android:keyEdgeFlags="left"/> <Key android:codes="8" android:keyLabel="1" android:keyEdgeFlags="left"/>
<Key android:codes="9" android:keyLabel="2"/> <Key android:codes="9" android:keyLabel="2"/>
<Key android:codes="10" android:keyLabel="3"/> <Key android:codes="10" android:keyLabel="3"/>
<Key android:codes="11" android:keyLabel="4"/> <Key android:codes="11" android:keyLabel="4"/>
<Key android:codes="12" android:keyLabel="5"/> <Key android:codes="12" android:keyLabel="5"/>
<Key android:codes="13" android:keyLabel="6"/> <Key android:codes="13" android:keyLabel="6"/>
<Key android:codes="14" android:keyLabel="7"/> <Key android:codes="14" android:keyLabel="7"/>
<Key android:codes="15" android:keyLabel="8"/> <Key android:codes="15" android:keyLabel="8"/>
<Key android:codes="16" android:keyLabel="9"/> <Key android:codes="16" android:keyLabel="9"/>
<Key android:codes="7" android:keyLabel="0" android:keyEdgeFlags="right"/> <Key android:codes="7" android:keyLabel="0" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="111" android:keyLabel="Esc" android:keyEdgeFlags="left"/> <Key android:codes="111" android:keyLabel="Esc" android:keyEdgeFlags="left"/>
<Key android:codes="68" android:keyLabel="`"/> <Key android:codes="68" android:keyLabel="`"/>
<Key android:codes="69" android:keyLabel="-"/> <Key android:codes="69" android:keyLabel="-"/>
<Key android:codes="70" android:keyLabel="="/> <Key android:codes="70" android:keyLabel="="/>
<Key android:codes="73" android:keyLabel="\\"/> <Key android:codes="73" android:keyLabel="\\"/>
<Key android:codes="124" android:keyLabel="Ins"/> <Key android:codes="124" android:keyLabel="Ins"/>
<Key android:codes="92" android:keyLabel="PgUp"/> <Key android:codes="92" android:keyLabel="PgUp"/>
<Key android:codes="122" android:keyLabel="Home"/> <Key android:codes="122" android:keyLabel="Home"/>
<Key android:codes="19" android:keyLabel="↑"/> <Key android:codes="19" android:keyLabel="↑"/>
<Key android:codes="123" android:keyLabel="End" android:keyEdgeFlags="right" /> <Key android:codes="123" android:keyLabel="End" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="-1" android:keyLabel="!@#…" android:keyEdgeFlags="left"/> <Key android:codes="-1" android:keyLabel="!@#…" android:keyEdgeFlags="left"/>
<Key android:codes="75" android:keyLabel="'"/> <Key android:codes="75" android:keyLabel="'"/>
<Key android:codes="100075" android:keyLabel="&quot;"/> <Key android:codes="100075" android:keyLabel="&quot;"/>
<Key android:codes="61" android:keyLabel="Tab"/> <Key android:codes="61" android:keyLabel="Tab"/>
<Key android:codes="115" android:keyLabel="CapsLk"/> <Key android:codes="115" android:keyLabel="CapsLk"/>
<Key android:codes="112" android:keyLabel="Del"/> <Key android:codes="112" android:keyLabel="Del"/>
<Key android:codes="93" android:keyLabel="PgDn"/> <Key android:codes="93" android:keyLabel="PgDn"/>
<Key android:codes="21" android:keyLabel="←"/> <Key android:codes="21" android:keyLabel="←"/>
<Key android:codes="20" android:keyLabel="↓"/> <Key android:codes="20" android:keyLabel="↓"/>
<Key android:codes="22" android:keyLabel="→" android:keyEdgeFlags="right"/> <Key android:codes="22" android:keyLabel="→" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row android:rowEdgeFlags="bottom"> <Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="abc…" android:keyEdgeFlags="left"/> <Key android:codes="-6" android:keyLabel="abc…" android:keyEdgeFlags="left"/>
<Key android:codes="59" android:keyLabel="Shift" android:isSticky="true"/> <Key android:codes="59" android:keyLabel="Shift" android:isSticky="true"/>
<Key android:codes="113" android:keyLabel="Ctrl" android:isSticky="true"/> <Key android:codes="113" android:keyLabel="Ctrl" android:isSticky="true"/>
<Key android:codes="117" android:keyLabel="Meta" android:isSticky="true"/> <Key android:codes="117" android:keyLabel="Meta" android:isSticky="true"/>
<Key android:codes="57" android:keyLabel="Alt" android:isSticky="true"/> <Key android:codes="57" android:keyLabel="Alt" android:isSticky="true"/>
<Key android:codes="58" android:keyLabel="Alt" android:isSticky="true"/> <Key android:codes="58" android:keyLabel="Alt" android:isSticky="true"/>
<Key android:codes="118" android:keyLabel="Meta" android:isSticky="true"/> <Key android:codes="118" android:keyLabel="Meta" android:isSticky="true"/>
<Key android:codes="226" android:keyLabel="Menu" android:isSticky="true"/> <Key android:codes="226" android:keyLabel="Menu" android:isSticky="true"/>
<Key android:codes="114" android:keyLabel="Ctrl" android:isSticky="true"/> <Key android:codes="114" android:keyLabel="Ctrl" android:isSticky="true"/>
<Key android:codes="60" android:keyLabel="Shift" android:isSticky="true"/> <Key android:codes="60" android:keyLabel="Shift" android:isSticky="true"/>
</Row> </Row>
</Keyboard> </Keyboard>

View File

@ -1,57 +1,57 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java --> <!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" <Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p" android:keyWidth="10%p"
android:horizontalGap="0px" android:horizontalGap="0px"
android:verticalGap="0px" android:verticalGap="0px"
android:keyHeight="10%p"> android:keyHeight="10%p">
<Row> <Row>
<Key android:codes="100008" android:keyLabel="!" android:keyEdgeFlags="left"/> <Key android:codes="100008" android:keyLabel="!" android:keyEdgeFlags="left"/>
<Key android:codes="100009" android:keyLabel="\@"/> <Key android:codes="100009" android:keyLabel="\@"/>
<Key android:codes="100010" android:keyLabel="#"/> <Key android:codes="100010" android:keyLabel="#"/>
<Key android:codes="100011" android:keyLabel="$"/> <Key android:codes="100011" android:keyLabel="$"/>
<Key android:codes="100012" android:keyLabel="%"/> <Key android:codes="100012" android:keyLabel="%"/>
<Key android:codes="100013" android:keyLabel="^"/> <Key android:codes="100013" android:keyLabel="^"/>
<Key android:codes="100014" android:keyLabel="&amp;"/> <Key android:codes="100014" android:keyLabel="&amp;"/>
<Key android:codes="100015" android:keyLabel="*"/> <Key android:codes="100015" android:keyLabel="*"/>
<Key android:codes="100016" android:keyLabel="("/> <Key android:codes="100016" android:keyLabel="("/>
<Key android:codes="100007" android:keyLabel=")" android:keyEdgeFlags="right"/> <Key android:codes="100007" android:keyLabel=")" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="111" android:keyLabel="Esc" android:keyEdgeFlags="left"/> <Key android:codes="111" android:keyLabel="Esc" android:keyEdgeFlags="left"/>
<Key android:codes="124" android:keyLabel="Help"/> <Key android:codes="124" android:keyLabel="Help"/>
<Key android:codes="100069" android:keyLabel="_"/> <Key android:codes="100069" android:keyLabel="_"/>
<Key android:codes="100070" android:keyLabel="+"/> <Key android:codes="100070" android:keyLabel="+"/>
<Key android:codes="100073" android:keyLabel="|"/> <Key android:codes="100073" android:keyLabel="|"/>
<Key android:codes="100068" android:keyLabel="~"/> <Key android:codes="100068" android:keyLabel="~"/>
<Key android:codes="131" android:keyLabel="F1"/> <Key android:codes="131" android:keyLabel="F1"/>
<Key android:codes="132" android:keyLabel="F2"/> <Key android:codes="132" android:keyLabel="F2"/>
<Key android:codes="133" android:keyLabel="F3"/> <Key android:codes="133" android:keyLabel="F3"/>
<Key android:codes="134" android:keyLabel="F4" android:keyEdgeFlags="right" /> <Key android:codes="134" android:keyLabel="F4" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="-1" android:keyLabel="123…" android:keyEdgeFlags="left"/> <Key android:codes="-1" android:keyLabel="123…" android:keyEdgeFlags="left"/>
<Key android:codes="143" android:keyLabel="NumLk" android:isSticky="true"/> <Key android:codes="143" android:keyLabel="NumLk" android:isSticky="true"/>
<Key android:codes="120" android:keyLabel="Print"/> <Key android:codes="120" android:keyLabel="Print"/>
<Key android:codes="116" android:keyLabel="ScrollLk"/> <Key android:codes="116" android:keyLabel="ScrollLk"/>
<Key android:codes="121" android:keyLabel="Pause"/> <Key android:codes="121" android:keyLabel="Pause"/>
<Key android:codes="158" android:keyLabel="Kp ."/> <Key android:codes="158" android:keyLabel="Kp ."/>
<Key android:codes="135" android:keyLabel="F5"/> <Key android:codes="135" android:keyLabel="F5"/>
<Key android:codes="136" android:keyLabel="F6"/> <Key android:codes="136" android:keyLabel="F6"/>
<Key android:codes="137" android:keyLabel="F7"/> <Key android:codes="137" android:keyLabel="F7"/>
<Key android:codes="138" android:keyLabel="F8" android:keyEdgeFlags="right"/> <Key android:codes="138" android:keyLabel="F8" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row android:rowEdgeFlags="bottom"> <Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="abc…" android:keyEdgeFlags="left"/> <Key android:codes="-6" android:keyLabel="abc…" android:keyEdgeFlags="left"/>
<Key android:codes="154" android:keyLabel="Kp /"/> <Key android:codes="154" android:keyLabel="Kp /"/>
<Key android:codes="155" android:keyLabel="Kp *"/> <Key android:codes="155" android:keyLabel="Kp *"/>
<Key android:codes="156" android:keyLabel="Kp -"/> <Key android:codes="156" android:keyLabel="Kp -"/>
<Key android:codes="157" android:keyLabel="Kp +"/> <Key android:codes="157" android:keyLabel="Kp +"/>
<Key android:codes="160" android:keyLabel="Kp ↵"/> <Key android:codes="160" android:keyLabel="Kp ↵"/>
<Key android:codes="139" android:keyLabel="F9" /> <Key android:codes="139" android:keyLabel="F9"/>
<Key android:codes="140" android:keyLabel="F10"/> <Key android:codes="140" android:keyLabel="F10"/>
<Key android:codes="141" android:keyLabel="F11"/> <Key android:codes="141" android:keyLabel="F11"/>
<Key android:codes="142" android:keyLabel="F12" android:keyEdgeFlags="right"/> <Key android:codes="142" android:keyLabel="F12" android:keyEdgeFlags="right"/>
</Row> </Row>
</Keyboard> </Keyboard>

View File

@ -1,90 +1,90 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java --> <!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" <Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p" android:keyWidth="10%p"
android:horizontalGap="0px" android:horizontalGap="0px"
android:verticalGap="0px" android:verticalGap="0px"
android:keyHeight="10%p"> android:keyHeight="10%p">
<Row> <Row>
<Key android:codes="111" android:keyLabel="ESC" android:keyEdgeFlags="left"/> <Key android:codes="111" android:keyLabel="ESC" android:keyEdgeFlags="left"/>
<Key android:codes="131" android:keyLabel="F1" /> <Key android:codes="131" android:keyLabel="F1"/>
<Key android:codes="132" android:keyLabel="F2" /> <Key android:codes="132" android:keyLabel="F2"/>
<Key android:codes="133" android:keyLabel="F3" /> <Key android:codes="133" android:keyLabel="F3"/>
<Key android:codes="134" android:keyLabel="F4" /> <Key android:codes="134" android:keyLabel="F4"/>
<Key android:codes="135" android:keyLabel="F5" /> <Key android:codes="135" android:keyLabel="F5"/>
<Key android:codes="136" android:keyLabel="F6" /> <Key android:codes="136" android:keyLabel="F6"/>
<Key android:codes="137" android:keyLabel="F7" /> <Key android:codes="137" android:keyLabel="F7"/>
<Key android:codes="138" android:keyLabel="F8" /> <Key android:codes="138" android:keyLabel="F8"/>
<Key android:codes="139" android:keyLabel="F9" /> <Key android:codes="139" android:keyLabel="F9"/>
<Key android:codes="140" android:keyLabel="F10" /> <Key android:codes="140" android:keyLabel="F10"/>
<Key android:codes="124" android:keyLabel="HELP" android:keyEdgeFlags="right"/> <Key android:codes="124" android:keyLabel="HELP" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="75" android:keyLabel="`" android:keyEdgeFlags="left"/> <Key android:codes="75" android:keyLabel="`" android:keyEdgeFlags="left"/>
<Key android:codes="8" android:keyLabel="1"/> <Key android:codes="8" android:keyLabel="1"/>
<Key android:codes="9" android:keyLabel="2"/> <Key android:codes="9" android:keyLabel="2"/>
<Key android:codes="10" android:keyLabel="3"/> <Key android:codes="10" android:keyLabel="3"/>
<Key android:codes="11" android:keyLabel="4"/> <Key android:codes="11" android:keyLabel="4"/>
<Key android:codes="12" android:keyLabel="5"/> <Key android:codes="12" android:keyLabel="5"/>
<Key android:codes="13" android:keyLabel="6"/> <Key android:codes="13" android:keyLabel="6"/>
<Key android:codes="14" android:keyLabel="7"/> <Key android:codes="14" android:keyLabel="7"/>
<Key android:codes="15" android:keyLabel="8"/> <Key android:codes="15" android:keyLabel="8"/>
<Key android:codes="16" android:keyLabel="9"/> <Key android:codes="16" android:keyLabel="9"/>
<Key android:codes="7" android:keyLabel="0"/> <Key android:codes="7" android:keyLabel="0"/>
<Key android:codes="67" android:keyLabel="DEL" android:keyEdgeFlags="right"/> <Key android:codes="67" android:keyLabel="DEL" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="61" android:keyLabel="TAB" android:keyEdgeFlags="left"/> <Key android:codes="61" android:keyLabel="TAB" android:keyEdgeFlags="left"/>
<Key android:codes="45" android:keyLabel="q"/> <Key android:codes="45" android:keyLabel="q"/>
<Key android:codes="51" android:keyLabel="w"/> <Key android:codes="51" android:keyLabel="w"/>
<Key android:codes="33" android:keyLabel="e"/> <Key android:codes="33" android:keyLabel="e"/>
<Key android:codes="46" android:keyLabel="r"/> <Key android:codes="46" android:keyLabel="r"/>
<Key android:codes="48" android:keyLabel="t"/> <Key android:codes="48" android:keyLabel="t"/>
<Key android:codes="53" android:keyLabel="y"/> <Key android:codes="53" android:keyLabel="y"/>
<Key android:codes="49" android:keyLabel="u"/> <Key android:codes="49" android:keyLabel="u"/>
<Key android:codes="37" android:keyLabel="i"/> <Key android:codes="37" android:keyLabel="i"/>
<Key android:codes="43" android:keyLabel="o"/> <Key android:codes="43" android:keyLabel="o"/>
<Key android:codes="44" android:keyLabel="p"/> <Key android:codes="44" android:keyLabel="p"/>
<Key android:codes="66" android:keyLabel="RET" android:keyEdgeFlags="right"/> <Key android:codes="66" android:keyLabel="RET" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="113" android:keyLabel="CTRL" android:keyEdgeFlags="left"/> <Key android:codes="113" android:keyLabel="CTRL" android:keyEdgeFlags="left"/>
<Key android:codes="115" android:keyLabel="CL"/> <Key android:codes="115" android:keyLabel="CL"/>
<Key android:codes="29" android:keyLabel="a"/> <Key android:codes="29" android:keyLabel="a"/>
<Key android:codes="47" android:keyLabel="s"/> <Key android:codes="47" android:keyLabel="s"/>
<Key android:codes="32" android:keyLabel="d"/> <Key android:codes="32" android:keyLabel="d"/>
<Key android:codes="34" android:keyLabel="f"/> <Key android:codes="34" android:keyLabel="f"/>
<Key android:codes="35" android:keyLabel="g"/> <Key android:codes="35" android:keyLabel="g"/>
<Key android:codes="36" android:keyLabel="h"/> <Key android:codes="36" android:keyLabel="h"/>
<Key android:codes="38" android:keyLabel="j"/> <Key android:codes="38" android:keyLabel="j"/>
<Key android:codes="39" android:keyLabel="k"/> <Key android:codes="39" android:keyLabel="k"/>
<Key android:codes="40" android:keyLabel="l"/> <Key android:codes="40" android:keyLabel="l"/>
<Key android:codes="74" android:keyLabel=";" android:keyEdgeFlags="right"/> <Key android:codes="74" android:keyLabel=";" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="59" android:keyLabel="SHFT" android:keyEdgeFlags="left"/> <Key android:codes="59" android:keyLabel="SHFT" android:keyEdgeFlags="left"/>
<Key android:codes="54" android:keyLabel="z"/> <Key android:codes="54" android:keyLabel="z"/>
<Key android:codes="52" android:keyLabel="x"/> <Key android:codes="52" android:keyLabel="x"/>
<Key android:codes="31" android:keyLabel="c"/> <Key android:codes="31" android:keyLabel="c"/>
<Key android:codes="50" android:keyLabel="v"/> <Key android:codes="50" android:keyLabel="v"/>
<Key android:codes="30" android:keyLabel="b"/> <Key android:codes="30" android:keyLabel="b"/>
<Key android:codes="42" android:keyLabel="n"/> <Key android:codes="42" android:keyLabel="n"/>
<Key android:codes="41" android:keyLabel="m"/> <Key android:codes="41" android:keyLabel="m"/>
<Key android:codes="55" android:keyLabel=","/> <Key android:codes="55" android:keyLabel=","/>
<Key android:codes="56" android:keyLabel="."/> <Key android:codes="56" android:keyLabel="."/>
<Key android:codes="76" android:keyLabel="/"/> <Key android:codes="76" android:keyLabel="/"/>
<Key android:codes="60" android:keyLabel="SHFT" android:keyEdgeFlags="right"/> <Key android:codes="60" android:keyLabel="SHFT" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row android:rowEdgeFlags="bottom"> <Row android:rowEdgeFlags="bottom">
<Key android:codes="57" android:keyLabel="ALT" android:keyEdgeFlags="left"/> <Key android:codes="57" android:keyLabel="ALT" android:keyEdgeFlags="left"/>
<Key android:codes="68" android:keyLabel="'" /> <Key android:codes="68" android:keyLabel="'"/>
<Key android:codes="73" android:keyLabel="\\"/> <Key android:codes="73" android:keyLabel="\\"/>
<Key android:codes="69" android:keyLabel="-"/> <Key android:codes="69" android:keyLabel="-"/>
<Key android:codes="62" android:keyLabel="SPACE" android:keyWidth="30%p" /> <Key android:codes="62" android:keyLabel="SPACE" android:keyWidth="30%p"/>
<Key android:codes="70" android:keyLabel="="/> <Key android:codes="70" android:keyLabel="="/>
<Key android:codes="71" android:keyLabel="["/> <Key android:codes="71" android:keyLabel="["/>
<Key android:codes="72" android:keyLabel="]"/> <Key android:codes="72" android:keyLabel="]"/>
<Key android:codes="58" android:keyLabel="ALT" android:keyEdgeFlags="right"/> <Key android:codes="58" android:keyLabel="ALT" android:keyEdgeFlags="right"/>
</Row> </Row>
</Keyboard> </Keyboard>

View File

@ -1,53 +1,55 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java --> <!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" <Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p" android:keyWidth="10%p"
android:horizontalGap="0px" android:horizontalGap="0px"
android:verticalGap="0px" android:verticalGap="0px"
android:keyHeight="10%p"> android:keyHeight="10%p">
<Row> <Row>
<Key android:codes="45" android:keyLabel="Q" android:keyEdgeFlags="left" android:isRepeatable="true"/> <Key android:codes="45" android:keyLabel="Q" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="51" android:keyLabel="W" android:isRepeatable="true"/> <Key android:codes="51" android:keyLabel="W" android:isRepeatable="true"/>
<Key android:codes="33" android:keyLabel="E" android:isRepeatable="true"/> <Key android:codes="33" android:keyLabel="E" android:isRepeatable="true"/>
<Key android:codes="46" android:keyLabel="R" android:isRepeatable="true"/> <Key android:codes="46" android:keyLabel="R" android:isRepeatable="true"/>
<Key android:codes="48" android:keyLabel="T" android:isRepeatable="true"/> <Key android:codes="48" android:keyLabel="T" android:isRepeatable="true"/>
<Key android:codes="53" android:keyLabel="Y" android:isRepeatable="true"/> <Key android:codes="53" android:keyLabel="Y" android:isRepeatable="true"/>
<Key android:codes="49" android:keyLabel="U" android:isRepeatable="true"/> <Key android:codes="49" android:keyLabel="U" android:isRepeatable="true"/>
<Key android:codes="37" android:keyLabel="I" android:isRepeatable="true"/> <Key android:codes="37" android:keyLabel="I" android:isRepeatable="true"/>
<Key android:codes="43" android:keyLabel="O" android:isRepeatable="true"/> <Key android:codes="43" android:keyLabel="O" android:isRepeatable="true"/>
<Key android:codes="44" android:keyLabel="P" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="44" android:keyLabel="P" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="29" android:keyLabel="A" android:keyEdgeFlags="left" android:isRepeatable="true"/> <Key android:codes="29" android:keyLabel="A" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="47" android:keyLabel="S" android:isRepeatable="true"/> <Key android:codes="47" android:keyLabel="S" android:isRepeatable="true"/>
<Key android:codes="32" android:keyLabel="D" android:isRepeatable="true"/> <Key android:codes="32" android:keyLabel="D" android:isRepeatable="true"/>
<Key android:codes="34" android:keyLabel="F" android:isRepeatable="true"/> <Key android:codes="34" android:keyLabel="F" android:isRepeatable="true"/>
<Key android:codes="35" android:keyLabel="G" android:isRepeatable="true"/> <Key android:codes="35" android:keyLabel="G" android:isRepeatable="true"/>
<Key android:codes="36" android:keyLabel="H" android:isRepeatable="true"/> <Key android:codes="36" android:keyLabel="H" android:isRepeatable="true"/>
<Key android:codes="38" android:keyLabel="J" android:isRepeatable="true"/> <Key android:codes="38" android:keyLabel="J" android:isRepeatable="true"/>
<Key android:codes="39" android:keyLabel="K" android:isRepeatable="true"/> <Key android:codes="39" android:keyLabel="K" android:isRepeatable="true"/>
<Key android:codes="40" android:keyLabel="L" android:isRepeatable="true"/> <Key android:codes="40" android:keyLabel="L" android:isRepeatable="true"/>
<Key android:codes="74" android:keyLabel=":" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="74" android:keyLabel=":" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="-1" android:keyLabel="⇫" android:keyEdgeFlags="left" android:isRepeatable="true"/> <Key android:codes="-1" android:keyLabel="⇫" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="54" android:keyLabel="Z" android:isRepeatable="true"/> <Key android:codes="54" android:keyLabel="Z" android:isRepeatable="true"/>
<Key android:codes="52" android:keyLabel="X" android:isRepeatable="true"/> <Key android:codes="52" android:keyLabel="X" android:isRepeatable="true"/>
<Key android:codes="31" android:keyLabel="C" android:isRepeatable="true"/> <Key android:codes="31" android:keyLabel="C" android:isRepeatable="true"/>
<Key android:codes="50" android:keyLabel="V" android:isRepeatable="true"/> <Key android:codes="50" android:keyLabel="V" android:isRepeatable="true"/>
<Key android:codes="30" android:keyLabel="B" android:isRepeatable="true"/> <Key android:codes="30" android:keyLabel="B" android:isRepeatable="true"/>
<Key android:codes="42" android:keyLabel="N" android:isRepeatable="true"/> <Key android:codes="42" android:keyLabel="N" android:isRepeatable="true"/>
<Key android:codes="41" android:keyLabel="M" android:isRepeatable="true"/> <Key android:codes="41" android:keyLabel="M" android:isRepeatable="true"/>
<Key android:codes="67" android:keyLabel="≪ ×" android:keyWidth="20%p" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="67" android:keyLabel="≪ ×" android:keyWidth="20%p" android:keyEdgeFlags="right"
</Row> android:isRepeatable="true"/>
<Row android:rowEdgeFlags="bottom"> </Row>
<Key android:codes="-6" android:keyLabel="!@#…" android:keyEdgeFlags="left" android:isRepeatable="true"/> <Row android:rowEdgeFlags="bottom">
<Key android:codes="71" android:keyLabel="{" android:isRepeatable="true"/> <Key android:codes="-6" android:keyLabel="!@#…" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="72" android:keyLabel="}" android:isRepeatable="true"/> <Key android:codes="71" android:keyLabel="{" android:isRepeatable="true"/>
<Key android:codes="76" android:keyLabel="\?" android:isRepeatable="true"/> <Key android:codes="72" android:keyLabel="}" android:isRepeatable="true"/>
<Key android:codes="62" android:keyLabel="Space" android:keyWidth="20%p" android:isRepeatable="true"/> <Key android:codes="76" android:keyLabel="\?" android:isRepeatable="true"/>
<Key android:codes="55" android:keyLabel="&lt;" android:isRepeatable="true"/> <Key android:codes="62" android:keyLabel="Space" android:keyWidth="20%p" android:isRepeatable="true"/>
<Key android:codes="56" android:keyLabel="&gt;" android:isRepeatable="true"/> <Key android:codes="55" android:keyLabel="&lt;" android:isRepeatable="true"/>
<Key android:codes="66" android:keyLabel="Enter" android:keyWidth="20%p" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="56" android:keyLabel="&gt;" android:isRepeatable="true"/>
</Row> <Key android:codes="66" android:keyLabel="Enter" android:keyWidth="20%p" android:keyEdgeFlags="right"
android:isRepeatable="true"/>
</Row>
</Keyboard> </Keyboard>

View File

@ -1,85 +1,85 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java --> <!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" <Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p" android:keyWidth="10%p"
android:horizontalGap="0px" android:horizontalGap="0px"
android:verticalGap="0px" android:verticalGap="0px"
android:keyHeight="10%p"> android:keyHeight="10%p">
<Row> <Row>
<Key android:codes="111" android:keyLabel="Esc" android:keyEdgeFlags="left"/> <Key android:codes="111" android:keyLabel="Esc" android:keyEdgeFlags="left"/>
<Key android:codes="122" android:keyLabel="Clear" /> <Key android:codes="122" android:keyLabel="Clear"/>
<Key android:codes="124" android:keyLabel="Insert" /> <Key android:codes="124" android:keyLabel="Insert"/>
<Key android:codes="123" android:keyLabel="Help" /> <Key android:codes="123" android:keyLabel="Help"/>
<Key android:codes="129" android:keyLabel="Start" /> <Key android:codes="129" android:keyLabel="Start"/>
<Key android:codes="128" android:keyLabel="Select" /> <Key android:codes="128" android:keyLabel="Select"/>
<Key android:codes="127" android:keyLabel="Option" /> <Key android:codes="127" android:keyLabel="Option"/>
<Key android:codes="130" android:keyLabel="Reset" android:keyEdgeFlags="right"/> <Key android:codes="130" android:keyLabel="Reset" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="75" android:keyLabel="`" android:keyEdgeFlags="left"/> <Key android:codes="75" android:keyLabel="`" android:keyEdgeFlags="left"/>
<Key android:codes="8" android:keyLabel="1"/> <Key android:codes="8" android:keyLabel="1"/>
<Key android:codes="9" android:keyLabel="2"/> <Key android:codes="9" android:keyLabel="2"/>
<Key android:codes="10" android:keyLabel="3"/> <Key android:codes="10" android:keyLabel="3"/>
<Key android:codes="11" android:keyLabel="4"/> <Key android:codes="11" android:keyLabel="4"/>
<Key android:codes="12" android:keyLabel="5"/> <Key android:codes="12" android:keyLabel="5"/>
<Key android:codes="13" android:keyLabel="6"/> <Key android:codes="13" android:keyLabel="6"/>
<Key android:codes="14" android:keyLabel="7"/> <Key android:codes="14" android:keyLabel="7"/>
<Key android:codes="15" android:keyLabel="8"/> <Key android:codes="15" android:keyLabel="8"/>
<Key android:codes="16" android:keyLabel="9"/> <Key android:codes="16" android:keyLabel="9"/>
<Key android:codes="7" android:keyLabel="0"/> <Key android:codes="7" android:keyLabel="0"/>
<Key android:codes="67" android:keyLabel="DEL" android:keyEdgeFlags="right"/> <Key android:codes="67" android:keyLabel="DEL" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="61" android:keyLabel="TAB" android:keyEdgeFlags="left"/> <Key android:codes="61" android:keyLabel="TAB" android:keyEdgeFlags="left"/>
<Key android:codes="45" android:keyLabel="q"/> <Key android:codes="45" android:keyLabel="q"/>
<Key android:codes="51" android:keyLabel="w"/> <Key android:codes="51" android:keyLabel="w"/>
<Key android:codes="33" android:keyLabel="e"/> <Key android:codes="33" android:keyLabel="e"/>
<Key android:codes="46" android:keyLabel="r"/> <Key android:codes="46" android:keyLabel="r"/>
<Key android:codes="48" android:keyLabel="t"/> <Key android:codes="48" android:keyLabel="t"/>
<Key android:codes="53" android:keyLabel="y"/> <Key android:codes="53" android:keyLabel="y"/>
<Key android:codes="49" android:keyLabel="u"/> <Key android:codes="49" android:keyLabel="u"/>
<Key android:codes="37" android:keyLabel="i"/> <Key android:codes="37" android:keyLabel="i"/>
<Key android:codes="43" android:keyLabel="o"/> <Key android:codes="43" android:keyLabel="o"/>
<Key android:codes="44" android:keyLabel="p"/> <Key android:codes="44" android:keyLabel="p"/>
<Key android:codes="66" android:keyLabel="RET" android:keyEdgeFlags="right"/> <Key android:codes="66" android:keyLabel="RET" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="113" android:keyLabel="CTRL" android:keyEdgeFlags="left"/> <Key android:codes="113" android:keyLabel="CTRL" android:keyEdgeFlags="left"/>
<Key android:codes="29" android:keyLabel="a"/> <Key android:codes="29" android:keyLabel="a"/>
<Key android:codes="47" android:keyLabel="s"/> <Key android:codes="47" android:keyLabel="s"/>
<Key android:codes="32" android:keyLabel="d"/> <Key android:codes="32" android:keyLabel="d"/>
<Key android:codes="34" android:keyLabel="f"/> <Key android:codes="34" android:keyLabel="f"/>
<Key android:codes="35" android:keyLabel="g"/> <Key android:codes="35" android:keyLabel="g"/>
<Key android:codes="36" android:keyLabel="h"/> <Key android:codes="36" android:keyLabel="h"/>
<Key android:codes="38" android:keyLabel="j"/> <Key android:codes="38" android:keyLabel="j"/>
<Key android:codes="39" android:keyLabel="k"/> <Key android:codes="39" android:keyLabel="k"/>
<Key android:codes="40" android:keyLabel="l"/> <Key android:codes="40" android:keyLabel="l"/>
<Key android:codes="74" android:keyLabel=";"/> <Key android:codes="74" android:keyLabel=";"/>
<Key android:codes="115" android:keyLabel="Caps" android:keyEdgeFlags="right"/> <Key android:codes="115" android:keyLabel="Caps" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="59" android:keyLabel="SHFT" android:keyEdgeFlags="left"/> <Key android:codes="59" android:keyLabel="SHFT" android:keyEdgeFlags="left"/>
<Key android:codes="54" android:keyLabel="z"/> <Key android:codes="54" android:keyLabel="z"/>
<Key android:codes="52" android:keyLabel="x"/> <Key android:codes="52" android:keyLabel="x"/>
<Key android:codes="31" android:keyLabel="c"/> <Key android:codes="31" android:keyLabel="c"/>
<Key android:codes="50" android:keyLabel="v"/> <Key android:codes="50" android:keyLabel="v"/>
<Key android:codes="30" android:keyLabel="b"/> <Key android:codes="30" android:keyLabel="b"/>
<Key android:codes="42" android:keyLabel="n"/> <Key android:codes="42" android:keyLabel="n"/>
<Key android:codes="41" android:keyLabel="m"/> <Key android:codes="41" android:keyLabel="m"/>
<Key android:codes="55" android:keyLabel=","/> <Key android:codes="55" android:keyLabel=","/>
<Key android:codes="56" android:keyLabel="."/> <Key android:codes="56" android:keyLabel="."/>
<Key android:codes="76" android:keyLabel="/"/> <Key android:codes="76" android:keyLabel="/"/>
<Key android:codes="60" android:keyLabel="SHFT" android:keyEdgeFlags="right"/> <Key android:codes="60" android:keyLabel="SHFT" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row android:rowEdgeFlags="bottom"> <Row android:rowEdgeFlags="bottom">
<Key android:codes="73" android:keyLabel="\\" android:keyEdgeFlags="left"/> <Key android:codes="73" android:keyLabel="\\" android:keyEdgeFlags="left"/>
<Key android:codes="69" android:keyLabel="-"/> <Key android:codes="69" android:keyLabel="-"/>
<Key android:codes="81" android:keyLabel="+"/> <Key android:codes="81" android:keyLabel="+"/>
<Key android:codes="70" android:keyLabel="="/> <Key android:codes="70" android:keyLabel="="/>
<Key android:codes="62" android:keyLabel="SPACE" android:keyWidth="30%p" /> <Key android:codes="62" android:keyLabel="SPACE" android:keyWidth="30%p"/>
<Key android:codes="71" android:keyLabel="["/> <Key android:codes="71" android:keyLabel="["/>
<Key android:codes="72" android:keyLabel="]"/> <Key android:codes="72" android:keyLabel="]"/>
<Key android:codes="18" android:keyLabel="#"/> <Key android:codes="18" android:keyLabel="#"/>
<Key android:codes="68" android:keyLabel="INV" android:keyEdgeFlags="right"/> <Key android:codes="68" android:keyLabel="INV" android:keyEdgeFlags="right"/>
</Row> </Row>
</Keyboard> </Keyboard>

View File

@ -1,85 +1,85 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java --> <!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" <Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p" android:keyWidth="10%p"
android:horizontalGap="0px" android:horizontalGap="0px"
android:verticalGap="0px" android:verticalGap="0px"
android:keyHeight="10%p"> android:keyHeight="10%p">
<Row> <Row>
<Key android:codes="113" android:keyLabel="C=" android:keyEdgeFlags="left"/> <Key android:codes="113" android:keyLabel="C=" android:keyEdgeFlags="left"/>
<Key android:codes="111" android:keyLabel="RS" /> <Key android:codes="111" android:keyLabel="RS"/>
<Key android:codes="5" android:keyLabel="CTRL" /> <Key android:codes="5" android:keyLabel="CTRL"/>
<Key android:codes="126" android:keyLabel="F1" /> <Key android:codes="126" android:keyLabel="F1"/>
<Key android:codes="103" android:keyLabel="F3" /> <Key android:codes="103" android:keyLabel="F3"/>
<Key android:codes="104" android:keyLabel="F5" /> <Key android:codes="104" android:keyLabel="F5"/>
<Key android:codes="105" android:keyLabel="F7" /> <Key android:codes="105" android:keyLabel="F7"/>
<Key android:codes="3" android:keyLabel="Home" /> <Key android:codes="3" android:keyLabel="Home"/>
<Key android:codes="71" android:keyLabel="\@" /> <Key android:codes="71" android:keyLabel="\@"/>
<Key android:codes="67" android:keyLabel="DEL" android:keyEdgeFlags="right"/> <Key android:codes="67" android:keyLabel="DEL" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="8" android:keyLabel="1" android:keyEdgeFlags="left"/> <Key android:codes="8" android:keyLabel="1" android:keyEdgeFlags="left"/>
<Key android:codes="9" android:keyLabel="2"/> <Key android:codes="9" android:keyLabel="2"/>
<Key android:codes="10" android:keyLabel="3"/> <Key android:codes="10" android:keyLabel="3"/>
<Key android:codes="11" android:keyLabel="4"/> <Key android:codes="11" android:keyLabel="4"/>
<Key android:codes="12" android:keyLabel="5"/> <Key android:codes="12" android:keyLabel="5"/>
<Key android:codes="13" android:keyLabel="6"/> <Key android:codes="13" android:keyLabel="6"/>
<Key android:codes="14" android:keyLabel="7"/> <Key android:codes="14" android:keyLabel="7"/>
<Key android:codes="15" android:keyLabel="8"/> <Key android:codes="15" android:keyLabel="8"/>
<Key android:codes="16" android:keyLabel="9"/> <Key android:codes="16" android:keyLabel="9"/>
<Key android:codes="7" android:keyLabel="0" /> <Key android:codes="7" android:keyLabel="0"/>
<Key android:codes="69" android:keyLabel="+" /> <Key android:codes="69" android:keyLabel="+"/>
<Key android:codes="70" android:keyLabel="-" android:keyEdgeFlags="right"/> <Key android:codes="70" android:keyLabel="-" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="45" android:keyLabel="q" android:keyEdgeFlags="left"/> <Key android:codes="45" android:keyLabel="q" android:keyEdgeFlags="left"/>
<Key android:codes="51" android:keyLabel="w"/> <Key android:codes="51" android:keyLabel="w"/>
<Key android:codes="33" android:keyLabel="e"/> <Key android:codes="33" android:keyLabel="e"/>
<Key android:codes="46" android:keyLabel="r"/> <Key android:codes="46" android:keyLabel="r"/>
<Key android:codes="48" android:keyLabel="t"/> <Key android:codes="48" android:keyLabel="t"/>
<Key android:codes="53" android:keyLabel="y"/> <Key android:codes="53" android:keyLabel="y"/>
<Key android:codes="49" android:keyLabel="u"/> <Key android:codes="49" android:keyLabel="u"/>
<Key android:codes="37" android:keyLabel="i"/> <Key android:codes="37" android:keyLabel="i"/>
<Key android:codes="43" android:keyLabel="o"/> <Key android:codes="43" android:keyLabel="o"/>
<Key android:codes="44" android:keyLabel="p"/> <Key android:codes="44" android:keyLabel="p"/>
<Key android:codes="72" android:keyLabel="*"/> <Key android:codes="72" android:keyLabel="*"/>
<Key android:codes="92" android:keyLabel="RST" android:keyEdgeFlags="right"/> <Key android:codes="92" android:keyLabel="RST" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="115" android:keyLabel="SL" android:keyEdgeFlags="left"/> <Key android:codes="115" android:keyLabel="SL" android:keyEdgeFlags="left"/>
<Key android:codes="29" android:keyLabel="a"/> <Key android:codes="29" android:keyLabel="a"/>
<Key android:codes="47" android:keyLabel="s"/> <Key android:codes="47" android:keyLabel="s"/>
<Key android:codes="32" android:keyLabel="d"/> <Key android:codes="32" android:keyLabel="d"/>
<Key android:codes="34" android:keyLabel="f"/> <Key android:codes="34" android:keyLabel="f"/>
<Key android:codes="35" android:keyLabel="g"/> <Key android:codes="35" android:keyLabel="g"/>
<Key android:codes="36" android:keyLabel="h"/> <Key android:codes="36" android:keyLabel="h"/>
<Key android:codes="38" android:keyLabel="j"/> <Key android:codes="38" android:keyLabel="j"/>
<Key android:codes="39" android:keyLabel="k"/> <Key android:codes="39" android:keyLabel="k"/>
<Key android:codes="40" android:keyLabel="l"/> <Key android:codes="40" android:keyLabel="l"/>
<Key android:codes="74" android:keyLabel=":"/> <Key android:codes="74" android:keyLabel=":"/>
<Key android:codes="75" android:keyLabel=";" android:keyEdgeFlags="right"/> <Key android:codes="75" android:keyLabel=";" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="59" android:keyLabel="SHFT" android:keyEdgeFlags="left"/> <Key android:codes="59" android:keyLabel="SHFT" android:keyEdgeFlags="left"/>
<Key android:codes="54" android:keyLabel="z"/> <Key android:codes="54" android:keyLabel="z"/>
<Key android:codes="52" android:keyLabel="x"/> <Key android:codes="52" android:keyLabel="x"/>
<Key android:codes="31" android:keyLabel="c"/> <Key android:codes="31" android:keyLabel="c"/>
<Key android:codes="50" android:keyLabel="v"/> <Key android:codes="50" android:keyLabel="v"/>
<Key android:codes="30" android:keyLabel="b"/> <Key android:codes="30" android:keyLabel="b"/>
<Key android:codes="42" android:keyLabel="n"/> <Key android:codes="42" android:keyLabel="n"/>
<Key android:codes="41" android:keyLabel="m"/> <Key android:codes="41" android:keyLabel="m"/>
<Key android:codes="55" android:keyLabel=","/> <Key android:codes="55" android:keyLabel=","/>
<Key android:codes="56" android:keyLabel="."/> <Key android:codes="56" android:keyLabel="."/>
<Key android:codes="76" android:keyLabel="/"/> <Key android:codes="76" android:keyLabel="/"/>
<Key android:codes="60" android:keyLabel="SHFT" android:keyEdgeFlags="right"/> <Key android:codes="60" android:keyLabel="SHFT" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row android:rowEdgeFlags="bottom"> <Row android:rowEdgeFlags="bottom">
<Key android:codes="124" android:keyLabel="GBP" android:keyWidth="10%p" android:keyEdgeFlags="left"/> <Key android:codes="124" android:keyLabel="GBP" android:keyWidth="10%p" android:keyEdgeFlags="left"/>
<Key android:codes="68" android:keyLabel="-" android:keyWidth="10%p"/> <Key android:codes="68" android:keyLabel="-" android:keyWidth="10%p"/>
<Key android:codes="112" android:keyLabel="|" android:keyWidth="10%p"/> <Key android:codes="112" android:keyLabel="|" android:keyWidth="10%p"/>
<Key android:codes="62" android:keyLabel="SPACE" android:keyWidth="40%p"/> <Key android:codes="62" android:keyLabel="SPACE" android:keyWidth="40%p"/>
<Key android:codes="73" android:keyLabel="=" android:keyWidth="10%p"/> <Key android:codes="73" android:keyLabel="=" android:keyWidth="10%p"/>
<Key android:codes="23" android:keyLabel="RETURN" android:keyWidth="20%p" android:keyEdgeFlags="right"/> <Key android:codes="23" android:keyLabel="RETURN" android:keyWidth="20%p" android:keyEdgeFlags="right"/>
</Row> </Row>
</Keyboard> </Keyboard>

View File

@ -1,53 +1,55 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java --> <!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" <Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p" android:keyWidth="10%p"
android:horizontalGap="0px" android:horizontalGap="0px"
android:verticalGap="0px" android:verticalGap="0px"
android:keyHeight="10%p"> android:keyHeight="10%p">
<Row> <Row>
<Key android:codes="45" android:keyLabel="q" android:keyEdgeFlags="left" android:isRepeatable="true"/> <Key android:codes="45" android:keyLabel="q" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="51" android:keyLabel="w" android:isRepeatable="true"/> <Key android:codes="51" android:keyLabel="w" android:isRepeatable="true"/>
<Key android:codes="33" android:keyLabel="e" android:isRepeatable="true"/> <Key android:codes="33" android:keyLabel="e" android:isRepeatable="true"/>
<Key android:codes="46" android:keyLabel="r" android:isRepeatable="true"/> <Key android:codes="46" android:keyLabel="r" android:isRepeatable="true"/>
<Key android:codes="48" android:keyLabel="t" android:isRepeatable="true"/> <Key android:codes="48" android:keyLabel="t" android:isRepeatable="true"/>
<Key android:codes="53" android:keyLabel="y" android:isRepeatable="true"/> <Key android:codes="53" android:keyLabel="y" android:isRepeatable="true"/>
<Key android:codes="49" android:keyLabel="u" android:isRepeatable="true"/> <Key android:codes="49" android:keyLabel="u" android:isRepeatable="true"/>
<Key android:codes="37" android:keyLabel="i" android:isRepeatable="true"/> <Key android:codes="37" android:keyLabel="i" android:isRepeatable="true"/>
<Key android:codes="43" android:keyLabel="o" android:isRepeatable="true"/> <Key android:codes="43" android:keyLabel="o" android:isRepeatable="true"/>
<Key android:codes="44" android:keyLabel="p" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="44" android:keyLabel="p" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="29" android:keyLabel="a" android:keyEdgeFlags="left" android:isRepeatable="true"/> <Key android:codes="29" android:keyLabel="a" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="47" android:keyLabel="s" android:isRepeatable="true"/> <Key android:codes="47" android:keyLabel="s" android:isRepeatable="true"/>
<Key android:codes="32" android:keyLabel="d" android:isRepeatable="true"/> <Key android:codes="32" android:keyLabel="d" android:isRepeatable="true"/>
<Key android:codes="34" android:keyLabel="f" android:isRepeatable="true"/> <Key android:codes="34" android:keyLabel="f" android:isRepeatable="true"/>
<Key android:codes="35" android:keyLabel="g" android:isRepeatable="true"/> <Key android:codes="35" android:keyLabel="g" android:isRepeatable="true"/>
<Key android:codes="36" android:keyLabel="h" android:isRepeatable="true"/> <Key android:codes="36" android:keyLabel="h" android:isRepeatable="true"/>
<Key android:codes="38" android:keyLabel="j" android:isRepeatable="true"/> <Key android:codes="38" android:keyLabel="j" android:isRepeatable="true"/>
<Key android:codes="39" android:keyLabel="k" android:isRepeatable="true"/> <Key android:codes="39" android:keyLabel="k" android:isRepeatable="true"/>
<Key android:codes="40" android:keyLabel="l" android:isRepeatable="true"/> <Key android:codes="40" android:keyLabel="l" android:isRepeatable="true"/>
<Key android:codes="74" android:keyLabel=";" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="74" android:keyLabel=";" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="-1" android:keyLabel="⇪" android:keyEdgeFlags="left"/> <Key android:codes="-1" android:keyLabel="⇪" android:keyEdgeFlags="left"/>
<Key android:codes="54" android:keyLabel="z" android:isRepeatable="true"/> <Key android:codes="54" android:keyLabel="z" android:isRepeatable="true"/>
<Key android:codes="52" android:keyLabel="x" android:isRepeatable="true"/> <Key android:codes="52" android:keyLabel="x" android:isRepeatable="true"/>
<Key android:codes="31" android:keyLabel="c" android:isRepeatable="true"/> <Key android:codes="31" android:keyLabel="c" android:isRepeatable="true"/>
<Key android:codes="50" android:keyLabel="v" android:isRepeatable="true"/> <Key android:codes="50" android:keyLabel="v" android:isRepeatable="true"/>
<Key android:codes="30" android:keyLabel="b" android:isRepeatable="true"/> <Key android:codes="30" android:keyLabel="b" android:isRepeatable="true"/>
<Key android:codes="42" android:keyLabel="n" android:isRepeatable="true"/> <Key android:codes="42" android:keyLabel="n" android:isRepeatable="true"/>
<Key android:codes="41" android:keyLabel="m" android:isRepeatable="true"/> <Key android:codes="41" android:keyLabel="m" android:isRepeatable="true"/>
<Key android:codes="67" android:keyLabel="≪ ×" android:keyWidth="20%p" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="67" android:keyLabel="≪ ×" android:keyWidth="20%p" android:keyEdgeFlags="right"
</Row> android:isRepeatable="true"/>
<Row android:rowEdgeFlags="bottom"> </Row>
<Key android:codes="-6" android:keyLabel="123…" android:keyEdgeFlags="left"/> <Row android:rowEdgeFlags="bottom">
<Key android:codes="71" android:keyLabel="[" android:isRepeatable="true"/> <Key android:codes="-6" android:keyLabel="123…" android:keyEdgeFlags="left"/>
<Key android:codes="72" android:keyLabel="]" android:isRepeatable="true"/> <Key android:codes="71" android:keyLabel="[" android:isRepeatable="true"/>
<Key android:codes="76" android:keyLabel="/" android:isRepeatable="true"/> <Key android:codes="72" android:keyLabel="]" android:isRepeatable="true"/>
<Key android:codes="62" android:keyLabel="Space" android:keyWidth="20%p" android:isRepeatable="true"/> <Key android:codes="76" android:keyLabel="/" android:isRepeatable="true"/>
<Key android:codes="55" android:keyLabel="," android:isRepeatable="true"/> <Key android:codes="62" android:keyLabel="Space" android:keyWidth="20%p" android:isRepeatable="true"/>
<Key android:codes="56" android:keyLabel="." android:isRepeatable="true"/> <Key android:codes="55" android:keyLabel="," android:isRepeatable="true"/>
<Key android:codes="66" android:keyLabel="Enter" android:keyWidth="20%p" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="56" android:keyLabel="." android:isRepeatable="true"/>
</Row> <Key android:codes="66" android:keyLabel="Enter" android:keyWidth="20%p" android:keyEdgeFlags="right"
android:isRepeatable="true"/>
</Row>
</Keyboard> </Keyboard>

View File

@ -1,57 +1,57 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java --> <!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" <Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p" android:keyWidth="10%p"
android:horizontalGap="0px" android:horizontalGap="0px"
android:verticalGap="0px" android:verticalGap="0px"
android:keyHeight="10%p"> android:keyHeight="10%p">
<Row> <Row>
<Key android:codes="8" android:keyLabel="1" android:keyEdgeFlags="left" android:isRepeatable="true"/> <Key android:codes="8" android:keyLabel="1" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="9" android:keyLabel="2" android:isRepeatable="true"/> <Key android:codes="9" android:keyLabel="2" android:isRepeatable="true"/>
<Key android:codes="10" android:keyLabel="3" android:isRepeatable="true"/> <Key android:codes="10" android:keyLabel="3" android:isRepeatable="true"/>
<Key android:codes="11" android:keyLabel="4" android:isRepeatable="true"/> <Key android:codes="11" android:keyLabel="4" android:isRepeatable="true"/>
<Key android:codes="12" android:keyLabel="5" android:isRepeatable="true"/> <Key android:codes="12" android:keyLabel="5" android:isRepeatable="true"/>
<Key android:codes="13" android:keyLabel="6" android:isRepeatable="true"/> <Key android:codes="13" android:keyLabel="6" android:isRepeatable="true"/>
<Key android:codes="14" android:keyLabel="7" android:isRepeatable="true"/> <Key android:codes="14" android:keyLabel="7" android:isRepeatable="true"/>
<Key android:codes="15" android:keyLabel="8" android:isRepeatable="true"/> <Key android:codes="15" android:keyLabel="8" android:isRepeatable="true"/>
<Key android:codes="16" android:keyLabel="9" android:isRepeatable="true"/> <Key android:codes="16" android:keyLabel="9" android:isRepeatable="true"/>
<Key android:codes="7" android:keyLabel="0" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="7" android:keyLabel="0" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="111" android:keyLabel="Esc" android:keyEdgeFlags="left" android:isRepeatable="true"/> <Key android:codes="111" android:keyLabel="Esc" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="68" android:keyLabel="`" android:isRepeatable="true"/> <Key android:codes="68" android:keyLabel="`" android:isRepeatable="true"/>
<Key android:codes="69" android:keyLabel="-" android:isRepeatable="true"/> <Key android:codes="69" android:keyLabel="-" android:isRepeatable="true"/>
<Key android:codes="70" android:keyLabel="=" android:isRepeatable="true"/> <Key android:codes="70" android:keyLabel="=" android:isRepeatable="true"/>
<Key android:codes="73" android:keyLabel="\\" android:isRepeatable="true"/> <Key android:codes="73" android:keyLabel="\\" android:isRepeatable="true"/>
<Key android:codes="124" android:keyLabel="Ins" android:isRepeatable="true"/> <Key android:codes="124" android:keyLabel="Ins" android:isRepeatable="true"/>
<Key android:codes="92" android:keyLabel="PgUp" android:isRepeatable="true"/> <Key android:codes="92" android:keyLabel="PgUp" android:isRepeatable="true"/>
<Key android:codes="122" android:keyLabel="Home" android:isRepeatable="true"/> <Key android:codes="122" android:keyLabel="Home" android:isRepeatable="true"/>
<Key android:codes="19" android:keyLabel="↑" android:isRepeatable="true"/> <Key android:codes="19" android:keyLabel="↑" android:isRepeatable="true"/>
<Key android:codes="123" android:keyLabel="End" android:isRepeatable="true" android:keyEdgeFlags="right" /> <Key android:codes="123" android:keyLabel="End" android:isRepeatable="true" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="-1" android:keyLabel="!@#…" android:keyEdgeFlags="left"/> <Key android:codes="-1" android:keyLabel="!@#…" android:keyEdgeFlags="left"/>
<Key android:codes="75" android:keyLabel="'" android:isRepeatable="true"/> <Key android:codes="75" android:keyLabel="'" android:isRepeatable="true"/>
<Key android:codes="100075" android:keyLabel="&quot;" android:isRepeatable="true"/> <Key android:codes="100075" android:keyLabel="&quot;" android:isRepeatable="true"/>
<Key android:codes="61" android:keyLabel="Tab" android:isRepeatable="true"/> <Key android:codes="61" android:keyLabel="Tab" android:isRepeatable="true"/>
<Key android:codes="115" android:keyLabel="CapsLk" android:isRepeatable="true"/> <Key android:codes="115" android:keyLabel="CapsLk" android:isRepeatable="true"/>
<Key android:codes="112" android:keyLabel="Del" android:isRepeatable="true"/> <Key android:codes="112" android:keyLabel="Del" android:isRepeatable="true"/>
<Key android:codes="93" android:keyLabel="PgDn" android:isRepeatable="true"/> <Key android:codes="93" android:keyLabel="PgDn" android:isRepeatable="true"/>
<Key android:codes="21" android:keyLabel="←" android:isRepeatable="true"/> <Key android:codes="21" android:keyLabel="←" android:isRepeatable="true"/>
<Key android:codes="20" android:keyLabel="↓" android:isRepeatable="true"/> <Key android:codes="20" android:keyLabel="↓" android:isRepeatable="true"/>
<Key android:codes="22" android:keyLabel="→" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="22" android:keyLabel="→" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row> </Row>
<Row android:rowEdgeFlags="bottom"> <Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="abc…" android:keyEdgeFlags="left"/> <Key android:codes="-6" android:keyLabel="abc…" android:keyEdgeFlags="left"/>
<Key android:codes="59" android:keyLabel="Shift" android:isSticky="true"/> <Key android:codes="59" android:keyLabel="Shift" android:isSticky="true"/>
<Key android:codes="113" android:keyLabel="Ctrl" android:isSticky="true"/> <Key android:codes="113" android:keyLabel="Ctrl" android:isSticky="true"/>
<Key android:codes="117" android:keyLabel="Meta" android:isSticky="true"/> <Key android:codes="117" android:keyLabel="Meta" android:isSticky="true"/>
<Key android:codes="57" android:keyLabel="Alt" android:isSticky="true"/> <Key android:codes="57" android:keyLabel="Alt" android:isSticky="true"/>
<Key android:codes="58" android:keyLabel="Alt" android:isSticky="true"/> <Key android:codes="58" android:keyLabel="Alt" android:isSticky="true"/>
<Key android:codes="118" android:keyLabel="Meta" android:isSticky="true"/> <Key android:codes="118" android:keyLabel="Meta" android:isSticky="true"/>
<Key android:codes="226" android:keyLabel="Menu" android:isRepeatable="true"/> <Key android:codes="226" android:keyLabel="Menu" android:isRepeatable="true"/>
<Key android:codes="114" android:keyLabel="Ctrl" android:isSticky="true"/> <Key android:codes="114" android:keyLabel="Ctrl" android:isSticky="true"/>
<Key android:codes="60" android:keyLabel="Shift" android:isSticky="true"/> <Key android:codes="60" android:keyLabel="Shift" android:isSticky="true"/>
</Row> </Row>
</Keyboard> </Keyboard>

View File

@ -1,57 +1,57 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java --> <!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" <Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p" android:keyWidth="10%p"
android:horizontalGap="0px" android:horizontalGap="0px"
android:verticalGap="0px" android:verticalGap="0px"
android:keyHeight="10%p"> android:keyHeight="10%p">
<Row> <Row>
<Key android:codes="100008" android:keyLabel="!" android:keyEdgeFlags="left" android:isRepeatable="true"/> <Key android:codes="100008" android:keyLabel="!" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="100009" android:keyLabel="\@" android:isRepeatable="true"/> <Key android:codes="100009" android:keyLabel="\@" android:isRepeatable="true"/>
<Key android:codes="100010" android:keyLabel="#" android:isRepeatable="true"/> <Key android:codes="100010" android:keyLabel="#" android:isRepeatable="true"/>
<Key android:codes="100011" android:keyLabel="$" android:isRepeatable="true"/> <Key android:codes="100011" android:keyLabel="$" android:isRepeatable="true"/>
<Key android:codes="100012" android:keyLabel="%" android:isRepeatable="true"/> <Key android:codes="100012" android:keyLabel="%" android:isRepeatable="true"/>
<Key android:codes="100013" android:keyLabel="^" android:isRepeatable="true"/> <Key android:codes="100013" android:keyLabel="^" android:isRepeatable="true"/>
<Key android:codes="100014" android:keyLabel="&amp;" android:isRepeatable="true"/> <Key android:codes="100014" android:keyLabel="&amp;" android:isRepeatable="true"/>
<Key android:codes="100015" android:keyLabel="*" android:isRepeatable="true"/> <Key android:codes="100015" android:keyLabel="*" android:isRepeatable="true"/>
<Key android:codes="100016" android:keyLabel="(" android:isRepeatable="true"/> <Key android:codes="100016" android:keyLabel="(" android:isRepeatable="true"/>
<Key android:codes="100007" android:keyLabel=")" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="100007" android:keyLabel=")" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="111" android:keyLabel="Esc" android:keyEdgeFlags="left" android:isRepeatable="true"/> <Key android:codes="111" android:keyLabel="Esc" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="100068" android:keyLabel="~" android:isRepeatable="true"/> <Key android:codes="100068" android:keyLabel="~" android:isRepeatable="true"/>
<Key android:codes="100069" android:keyLabel="_" android:isRepeatable="true"/> <Key android:codes="100069" android:keyLabel="_" android:isRepeatable="true"/>
<Key android:codes="100070" android:keyLabel="+" android:isRepeatable="true"/> <Key android:codes="100070" android:keyLabel="+" android:isRepeatable="true"/>
<Key android:codes="100073" android:keyLabel="|" android:isRepeatable="true"/> <Key android:codes="100073" android:keyLabel="|" android:isRepeatable="true"/>
<Key android:codes="100075" android:keyLabel="&quot;" android:isRepeatable="true"/> <Key android:codes="100075" android:keyLabel="&quot;" android:isRepeatable="true"/>
<Key android:codes="131" android:keyLabel="F1" android:isRepeatable="true"/> <Key android:codes="131" android:keyLabel="F1" android:isRepeatable="true"/>
<Key android:codes="132" android:keyLabel="F2" android:isRepeatable="true"/> <Key android:codes="132" android:keyLabel="F2" android:isRepeatable="true"/>
<Key android:codes="133" android:keyLabel="F3" android:isRepeatable="true"/> <Key android:codes="133" android:keyLabel="F3" android:isRepeatable="true"/>
<Key android:codes="134" android:keyLabel="F4" android:isRepeatable="true" android:keyEdgeFlags="right" /> <Key android:codes="134" android:keyLabel="F4" android:isRepeatable="true" android:keyEdgeFlags="right"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="-1" android:keyLabel="123…" android:keyEdgeFlags="left"/> <Key android:codes="-1" android:keyLabel="123…" android:keyEdgeFlags="left"/>
<Key android:codes="143" android:keyLabel="NumLk" android:isSticky="true"/> <Key android:codes="143" android:keyLabel="NumLk" android:isSticky="true"/>
<Key android:codes="120" android:keyLabel="Print" android:isRepeatable="true"/> <Key android:codes="120" android:keyLabel="Print" android:isRepeatable="true"/>
<Key android:codes="116" android:keyLabel="ScrollLk" android:isRepeatable="true"/> <Key android:codes="116" android:keyLabel="ScrollLk" android:isRepeatable="true"/>
<Key android:codes="121" android:keyLabel="Pause" android:isRepeatable="true"/> <Key android:codes="121" android:keyLabel="Pause" android:isRepeatable="true"/>
<Key android:codes="158" android:keyLabel="Kp ." android:isRepeatable="true"/> <Key android:codes="158" android:keyLabel="Kp ." android:isRepeatable="true"/>
<Key android:codes="135" android:keyLabel="F5" android:isRepeatable="true"/> <Key android:codes="135" android:keyLabel="F5" android:isRepeatable="true"/>
<Key android:codes="136" android:keyLabel="F6" android:isRepeatable="true"/> <Key android:codes="136" android:keyLabel="F6" android:isRepeatable="true"/>
<Key android:codes="137" android:keyLabel="F7" android:isRepeatable="true"/> <Key android:codes="137" android:keyLabel="F7" android:isRepeatable="true"/>
<Key android:codes="138" android:keyLabel="F8" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="138" android:keyLabel="F8" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row> </Row>
<Row android:rowEdgeFlags="bottom"> <Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="abc…" android:keyEdgeFlags="left"/> <Key android:codes="-6" android:keyLabel="abc…" android:keyEdgeFlags="left"/>
<Key android:codes="154" android:keyLabel="Kp /" android:isRepeatable="true"/> <Key android:codes="154" android:keyLabel="Kp /" android:isRepeatable="true"/>
<Key android:codes="155" android:keyLabel="Kp *" android:isRepeatable="true"/> <Key android:codes="155" android:keyLabel="Kp *" android:isRepeatable="true"/>
<Key android:codes="156" android:keyLabel="Kp -" android:isRepeatable="true"/> <Key android:codes="156" android:keyLabel="Kp -" android:isRepeatable="true"/>
<Key android:codes="157" android:keyLabel="Kp +" android:isRepeatable="true"/> <Key android:codes="157" android:keyLabel="Kp +" android:isRepeatable="true"/>
<Key android:codes="160" android:keyLabel="Kp ↵" android:isRepeatable="true"/> <Key android:codes="160" android:keyLabel="Kp ↵" android:isRepeatable="true"/>
<Key android:codes="139" android:keyLabel="F9" android:isRepeatable="true"/> <Key android:codes="139" android:keyLabel="F9" android:isRepeatable="true"/>
<Key android:codes="140" android:keyLabel="F10" android:isRepeatable="true"/> <Key android:codes="140" android:keyLabel="F10" android:isRepeatable="true"/>
<Key android:codes="141" android:keyLabel="F11" android:isRepeatable="true"/> <Key android:codes="141" android:keyLabel="F11" android:isRepeatable="true"/>
<Key android:codes="142" android:keyLabel="F12" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="142" android:keyLabel="F12" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row> </Row>
</Keyboard> </Keyboard>

View File

@ -1,53 +1,55 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java --> <!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" <Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p" android:keyWidth="10%p"
android:horizontalGap="0px" android:horizontalGap="0px"
android:verticalGap="0px" android:verticalGap="0px"
android:keyHeight="10%p"> android:keyHeight="10%p">
<Row> <Row>
<Key android:codes="45" android:keyLabel="Q" android:keyEdgeFlags="left" android:isRepeatable="true"/> <Key android:codes="45" android:keyLabel="Q" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="51" android:keyLabel="W" android:isRepeatable="true"/> <Key android:codes="51" android:keyLabel="W" android:isRepeatable="true"/>
<Key android:codes="33" android:keyLabel="E" android:isRepeatable="true"/> <Key android:codes="33" android:keyLabel="E" android:isRepeatable="true"/>
<Key android:codes="46" android:keyLabel="R" android:isRepeatable="true"/> <Key android:codes="46" android:keyLabel="R" android:isRepeatable="true"/>
<Key android:codes="48" android:keyLabel="T" android:isRepeatable="true"/> <Key android:codes="48" android:keyLabel="T" android:isRepeatable="true"/>
<Key android:codes="53" android:keyLabel="Y" android:isRepeatable="true"/> <Key android:codes="53" android:keyLabel="Y" android:isRepeatable="true"/>
<Key android:codes="49" android:keyLabel="U" android:isRepeatable="true"/> <Key android:codes="49" android:keyLabel="U" android:isRepeatable="true"/>
<Key android:codes="37" android:keyLabel="I" android:isRepeatable="true"/> <Key android:codes="37" android:keyLabel="I" android:isRepeatable="true"/>
<Key android:codes="43" android:keyLabel="O" android:isRepeatable="true"/> <Key android:codes="43" android:keyLabel="O" android:isRepeatable="true"/>
<Key android:codes="44" android:keyLabel="P" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="44" android:keyLabel="P" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="29" android:keyLabel="A" android:keyEdgeFlags="left" android:isRepeatable="true"/> <Key android:codes="29" android:keyLabel="A" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="47" android:keyLabel="S" android:isRepeatable="true"/> <Key android:codes="47" android:keyLabel="S" android:isRepeatable="true"/>
<Key android:codes="32" android:keyLabel="D" android:isRepeatable="true"/> <Key android:codes="32" android:keyLabel="D" android:isRepeatable="true"/>
<Key android:codes="34" android:keyLabel="F" android:isRepeatable="true"/> <Key android:codes="34" android:keyLabel="F" android:isRepeatable="true"/>
<Key android:codes="35" android:keyLabel="G" android:isRepeatable="true"/> <Key android:codes="35" android:keyLabel="G" android:isRepeatable="true"/>
<Key android:codes="36" android:keyLabel="H" android:isRepeatable="true"/> <Key android:codes="36" android:keyLabel="H" android:isRepeatable="true"/>
<Key android:codes="38" android:keyLabel="J" android:isRepeatable="true"/> <Key android:codes="38" android:keyLabel="J" android:isRepeatable="true"/>
<Key android:codes="39" android:keyLabel="K" android:isRepeatable="true"/> <Key android:codes="39" android:keyLabel="K" android:isRepeatable="true"/>
<Key android:codes="40" android:keyLabel="L" android:isRepeatable="true"/> <Key android:codes="40" android:keyLabel="L" android:isRepeatable="true"/>
<Key android:codes="74" android:keyLabel=":" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="74" android:keyLabel=":" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row> </Row>
<Row> <Row>
<Key android:codes="-1" android:keyLabel="⇫" android:keyEdgeFlags="left"/> <Key android:codes="-1" android:keyLabel="⇫" android:keyEdgeFlags="left"/>
<Key android:codes="54" android:keyLabel="Z" android:isRepeatable="true"/> <Key android:codes="54" android:keyLabel="Z" android:isRepeatable="true"/>
<Key android:codes="52" android:keyLabel="X" android:isRepeatable="true"/> <Key android:codes="52" android:keyLabel="X" android:isRepeatable="true"/>
<Key android:codes="31" android:keyLabel="C" android:isRepeatable="true"/> <Key android:codes="31" android:keyLabel="C" android:isRepeatable="true"/>
<Key android:codes="50" android:keyLabel="V" android:isRepeatable="true"/> <Key android:codes="50" android:keyLabel="V" android:isRepeatable="true"/>
<Key android:codes="30" android:keyLabel="B" android:isRepeatable="true"/> <Key android:codes="30" android:keyLabel="B" android:isRepeatable="true"/>
<Key android:codes="42" android:keyLabel="N" android:isRepeatable="true"/> <Key android:codes="42" android:keyLabel="N" android:isRepeatable="true"/>
<Key android:codes="41" android:keyLabel="M" android:isRepeatable="true"/> <Key android:codes="41" android:keyLabel="M" android:isRepeatable="true"/>
<Key android:codes="67" android:keyLabel="≪ ×" android:keyWidth="20%p" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="67" android:keyLabel="≪ ×" android:keyWidth="20%p" android:keyEdgeFlags="right"
</Row> android:isRepeatable="true"/>
<Row android:rowEdgeFlags="bottom"> </Row>
<Key android:codes="-6" android:keyLabel="!@#…" android:keyEdgeFlags="left"/> <Row android:rowEdgeFlags="bottom">
<Key android:codes="71" android:keyLabel="{" android:isRepeatable="true"/> <Key android:codes="-6" android:keyLabel="!@#…" android:keyEdgeFlags="left"/>
<Key android:codes="72" android:keyLabel="}" android:isRepeatable="true"/> <Key android:codes="71" android:keyLabel="{" android:isRepeatable="true"/>
<Key android:codes="76" android:keyLabel="\?" android:isRepeatable="true"/> <Key android:codes="72" android:keyLabel="}" android:isRepeatable="true"/>
<Key android:codes="62" android:keyLabel="Space" android:keyWidth="20%p" android:isRepeatable="true"/> <Key android:codes="76" android:keyLabel="\?" android:isRepeatable="true"/>
<Key android:codes="55" android:keyLabel="&lt;" android:isRepeatable="true"/> <Key android:codes="62" android:keyLabel="Space" android:keyWidth="20%p" android:isRepeatable="true"/>
<Key android:codes="56" android:keyLabel="&gt;" android:isRepeatable="true"/> <Key android:codes="55" android:keyLabel="&lt;" android:isRepeatable="true"/>
<Key android:codes="66" android:keyLabel="Enter" android:keyWidth="20%p" android:keyEdgeFlags="right" android:isRepeatable="true"/> <Key android:codes="56" android:keyLabel="&gt;" android:isRepeatable="true"/>
</Row> <Key android:codes="66" android:keyLabel="Enter" android:keyWidth="20%p" android:keyEdgeFlags="right"
android:isRepeatable="true"/>
</Row>
</Keyboard> </Keyboard>

View File

@ -2,7 +2,7 @@ package io.neoterm;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
/** /**
* Example local unit test, which will execute on the development machine (host). * Example local unit test, which will execute on the development machine (host).
@ -10,8 +10,8 @@ import static org.junit.Assert.*;
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/ */
public class ExampleUnitTest { public class ExampleUnitTest {
@Test @Test
public void addition_isCorrect() throws Exception { public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2); assertEquals(4, 2 + 2);
} }
} }

View File

@ -2,70 +2,70 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
android { android {
compileSdkVersion rootProject.ext.android.COMPILE_SDK_VERSION compileSdkVersion rootProject.ext.android.COMPILE_SDK_VERSION
defaultConfig { defaultConfig {
applicationId "io.neoterm" applicationId "io.neoterm"
minSdkVersion rootProject.ext.android.MIN_SDK_VERSION minSdkVersion rootProject.ext.android.MIN_SDK_VERSION
targetSdkVersion rootProject.ext.android.TARGET_SDK_VERSION targetSdkVersion rootProject.ext.android.TARGET_SDK_VERSION
versionCode 37 versionCode 37
versionName "2.0.5" versionName "2.0.5"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
resConfigs "zh-rCN", "zh-rTW" resConfigs "zh-rCN", "zh-rTW"
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
abiFilters 'arm64-v8a'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
}
buildTypes {
release {
minifyEnabled true
zipAlignEnabled true
shrinkResources true
}
}
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path "CMakeLists.txt" cppFlags "-std=c++11"
} abiFilters 'arm64-v8a'
}
} }
lintOptions { sourceSets {
abortOnError false main {
checkReleaseBuilds false jniLibs.srcDirs = ['src/main/jniLibs']
}
} }
compileOptions { }
targetCompatibility 1.8 buildTypes {
sourceCompatibility 1.8 release {
minifyEnabled true
zipAlignEnabled true
shrinkResources true
} }
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
lintOptions {
abortOnError false
checkReleaseBuilds false
}
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
} }
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation rootProject.ext.deps["junit"] testImplementation rootProject.ext.deps["junit"]
androidTestImplementation project(path: ':NeoLang') androidTestImplementation project(path: ':NeoLang')
implementation rootProject.ext.deps["kotlin-stdlib"] implementation rootProject.ext.deps["kotlin-stdlib"]
implementation 'org.greenrobot:eventbus:3.0.0' implementation 'org.greenrobot:eventbus:3.0.0'
implementation 'com.github.wrdlbrnft:modular-adapter:0.2.0.6' implementation 'com.github.wrdlbrnft:modular-adapter:0.2.0.6'
implementation 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.19' implementation 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.19'
implementation 'com.simplecityapps:recyclerview-fastscroll:1.0.16' implementation 'com.simplecityapps:recyclerview-fastscroll:1.0.16'
implementation 'de.psdev.licensesdialog:licensesdialog:1.8.3' implementation 'de.psdev.licensesdialog:licensesdialog:1.8.3'
implementation 'com.github.GrenderG:Color-O-Matic:1.1.5' implementation 'com.github.GrenderG:Color-O-Matic:1.1.5'
implementation 'androidx.annotation:annotation:1.2.0' implementation 'androidx.annotation:annotation:1.2.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.appcompat:appcompat-resources:1.2.0' implementation 'androidx.appcompat:appcompat-resources:1.2.0'
implementation project(':chrome-tabs') implementation project(':chrome-tabs')
implementation project(':NeoLang') implementation project(':NeoLang')
implementation project(':NeoTermBridge') implementation project(':NeoTermBridge')
implementation project(':Xorg') implementation project(':Xorg')
} }

View File

@ -1,194 +1,194 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.neoterm"> package="io.neoterm">
<uses-feature <uses-feature
android:name="android.hardware.touchscreen" android:name="android.hardware.touchscreen"
android:required="false" /> android:required="false"/>
<uses-feature <uses-feature
android:name="android.software.leanback" android:name="android.software.leanback"
android:required="false" /> android:required="false"/>
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application <application
android:name=".App" android:name=".App"
android:allowBackup="true" android:allowBackup="true"
android:banner="@drawable/banner" android:banner="@drawable/banner"
android:extractNativeLibs="true" android:extractNativeLibs="true"
android:fullBackupContent="@xml/backup_config" android:fullBackupContent="@xml/backup_config"
android:icon="@mipmap/ic_launcher_neoterm_round" android:icon="@mipmap/ic_launcher_neoterm_round"
android:label="@string/app_name" android:label="@string/app_name"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:resizeableActivity="true" android:resizeableActivity="true"
android:roundIcon="@mipmap/ic_launcher_neoterm_round" android:roundIcon="@mipmap/ic_launcher_neoterm_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity <activity
android:name=".ui.term.NeoTermActivity" android:name=".ui.term.NeoTermActivity"
android:configChanges="orientation|keyboardHidden|screenSize" android:configChanges="orientation|keyboardHidden|screenSize"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/AppTheme.NoActionBar.Dark" android:theme="@style/AppTheme.NoActionBar.Dark"
android:windowSoftInputMode="adjustResize|stateHidden"> android:windowSoftInputMode="adjustResize|stateHidden">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LEANBACK_LAUNCHER" /> <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.app.shortcuts" android:name="android.app.shortcuts"
android:resource="@xml/app_shortcuts" /> android:resource="@xml/app_shortcuts"/>
</activity> </activity>
<activity-alias <activity-alias
android:name=".NeoLotMainActivity" android:name=".NeoLotMainActivity"
android:targetActivity="io.neoterm.ui.term.NeoTermActivity"> android:targetActivity="io.neoterm.ui.term.NeoTermActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.IOT_LAUNCHER" /> <category android:name="android.intent.category.IOT_LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
</activity-alias> </activity-alias>
<activity <activity
android:name=".ui.term.NeoTermRemoteInterface" android:name=".ui.term.NeoTermRemoteInterface"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="true" android:exported="true"
android:theme="@style/AppTheme.Dark" android:theme="@style/AppTheme.Dark"
android:windowSoftInputMode="adjustResize|stateHidden"> android:windowSoftInputMode="adjustResize|stateHidden">
<intent-filter> <intent-filter>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity-alias <activity-alias
android:name=".ui.term.TermHere" android:name=".ui.term.TermHere"
android:exported="true" android:exported="true"
android:label="@string/term_here" android:label="@string/term_here"
android:targetActivity=".ui.term.NeoTermRemoteInterface"> android:targetActivity=".ui.term.NeoTermRemoteInterface">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN"/>
<data android:mimeType="*/*" /> <data android:mimeType="*/*"/>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
</activity-alias> </activity-alias>
<activity-alias <activity-alias
android:name=".ui.term.UserScript" android:name=".ui.term.UserScript"
android:exported="true" android:exported="true"
android:label="@string/user_script" android:label="@string/user_script"
android:targetActivity=".ui.term.NeoTermRemoteInterface"> android:targetActivity=".ui.term.NeoTermRemoteInterface">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SEND_MULTIPLE" /> <action android:name="android.intent.action.SEND_MULTIPLE"/>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN"/>
<data android:mimeType="*/*" /> <data android:mimeType="*/*"/>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<data android:scheme="file" /> <data android:scheme="file"/>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW"/>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN"/>
<data android:mimeType="*/*" /> <data android:mimeType="*/*"/>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http" /> <data android:scheme="http"/>
<data android:scheme="https" /> <data android:scheme="https"/>
<data android:scheme="ftp" /> <data android:scheme="ftp"/>
<data android:mimeType="application/*" /> <data android:mimeType="application/*"/>
<data android:mimeType="audio/*" /> <data android:mimeType="audio/*"/>
<data android:mimeType="video/*" /> <data android:mimeType="video/*"/>
</intent-filter> </intent-filter>
</activity-alias> </activity-alias>
<activity <activity
android:name=".ui.support.AboutActivity" android:name=".ui.support.AboutActivity"
android:exported="false" android:exported="false"
android:label="@string/about" android:label="@string/about"
android:theme="@style/AppTheme.NoActionBar.Dark" /> android:theme="@style/AppTheme.NoActionBar.Dark"/>
<activity <activity
android:name=".ui.crash.CrashActivity" android:name=".ui.crash.CrashActivity"
android:exported="false" android:exported="false"
android:label="@string/error" android:label="@string/error"
android:theme="@style/AppTheme.NoActionBar.Dark" /> android:theme="@style/AppTheme.NoActionBar.Dark"/>
<activity <activity
android:name=".ui.setup.SetupActivity" android:name=".ui.setup.SetupActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true" android:exported="true"
android:theme="@style/AppTheme.NoActionBar" /> android:theme="@style/AppTheme.NoActionBar"/>
<activity <activity
android:name=".ui.bonus.BonusActivity" android:name=".ui.bonus.BonusActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:exported="false" android:exported="false"
android:theme="@style/AppTheme.NoActionBar.Dark" /> android:theme="@style/AppTheme.NoActionBar.Dark"/>
<activity <activity
android:name=".ui.pm.PackageManagerActivity" android:name=".ui.pm.PackageManagerActivity"
android:exported="false" android:exported="false"
android:label="@string/package_settings" android:label="@string/package_settings"
android:theme="@style/AppTheme.NoActionBar.Dark" /> android:theme="@style/AppTheme.NoActionBar.Dark"/>
<activity <activity
android:name=".ui.customize.CustomizeActivity" android:name=".ui.customize.CustomizeActivity"
android:exported="false" android:exported="false"
android:label="@string/customization_settings" android:label="@string/customization_settings"
android:theme="@style/AppTheme.NoActionBar.Dark" /> android:theme="@style/AppTheme.NoActionBar.Dark"/>
<activity <activity
android:name=".ui.customize.ColorSchemeActivity" android:name=".ui.customize.ColorSchemeActivity"
android:exported="false" android:exported="false"
android:label="@string/pref_customization_color_scheme" android:label="@string/pref_customization_color_scheme"
android:theme="@style/AppTheme.NoActionBar.Dark" /> android:theme="@style/AppTheme.NoActionBar.Dark"/>
<activity <activity
android:name=".ui.support.HelpActivity" android:name=".ui.support.HelpActivity"
android:exported="false" android:exported="false"
android:label="@string/faq" android:label="@string/faq"
android:theme="@style/AppTheme.NoActionBar.Dark" /> android:theme="@style/AppTheme.NoActionBar.Dark"/>
<activity <activity
android:name=".ui.settings.SettingActivity" android:name=".ui.settings.SettingActivity"
android:exported="false" android:exported="false"
android:theme="@style/AppTheme.Dark" /> android:theme="@style/AppTheme.Dark"/>
<activity <activity
android:name=".ui.settings.GeneralSettingsActivity" android:name=".ui.settings.GeneralSettingsActivity"
android:exported="false" android:exported="false"
android:theme="@style/AppTheme.Dark" /> android:theme="@style/AppTheme.Dark"/>
<activity <activity
android:name=".ui.settings.UISettingsActivity" android:name=".ui.settings.UISettingsActivity"
android:exported="false" android:exported="false"
android:theme="@style/AppTheme.Dark" /> android:theme="@style/AppTheme.Dark"/>
<service <service
android:name=".services.NeoTermService" android:name=".services.NeoTermService"
android:enabled="true" /> android:enabled="true"/>
<meta-data <meta-data
android:name="com.sec.android.support.multiwindow" android:name="com.sec.android.support.multiwindow"
android:value="true" /> android:value="true"/>
<meta-data <meta-data
android:name="com.lge.support.SPLIT_WINDOW" android:name="com.lge.support.SPLIT_WINDOW"
android:value="true" /> android:value="true"/>
</application> </application>
</manifest> </manifest>

View File

@ -17,60 +17,60 @@ import io.neoterm.utils.CrashHandler
* @author kiva * @author kiva
*/ */
class App : Application() { class App : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
app = this app = this
NeoPreference.init(this) NeoPreference.init(this)
CrashHandler.init() CrashHandler.init()
NeoInitializer.init(this) NeoInitializer.init(this)
}
fun errorDialog(context: Context, message: Int, dismissCallback: (() -> Unit)?) {
errorDialog(context, getString(message), dismissCallback)
}
fun errorDialog(context: Context, message: String, dismissCallback: (() -> Unit)?) {
AlertDialog.Builder(context)
.setTitle(R.string.error)
.setMessage(message)
.setNegativeButton(android.R.string.no, null)
.setPositiveButton(R.string.show_help) { _, _ ->
openHelpLink()
}
.setOnDismissListener {
dismissCallback?.invoke()
}
.show()
}
fun openHelpLink() {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://neoterm.gitbooks.io/neoterm-wiki/content/"))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
fun easterEgg(context: Context, message: String) {
val happyCount = NeoPreference.loadInt(NeoPreference.KEY_HAPPY_EGG, 0) + 1
NeoPreference.store(NeoPreference.KEY_HAPPY_EGG, happyCount)
val trigger = NeoPreference.VALUE_HAPPY_EGG_TRIGGER
if (happyCount == trigger / 2) {
@SuppressLint("ShowToast")
val toast = Toast.makeText(this, message, Toast.LENGTH_LONG)
toast.setGravity(Gravity.CENTER, 0, 0)
toast.show()
} else if (happyCount > trigger) {
NeoPreference.store(NeoPreference.KEY_HAPPY_EGG, 0)
context.startActivity(Intent(context, BonusActivity::class.java))
} }
}
fun errorDialog(context: Context, message: Int, dismissCallback: (() -> Unit)?) { companion object {
errorDialog(context, getString(message), dismissCallback) private var app: App? = null
}
fun get(): App {
fun errorDialog(context: Context, message: String, dismissCallback: (() -> Unit)?) { return app!!
AlertDialog.Builder(context)
.setTitle(R.string.error)
.setMessage(message)
.setNegativeButton(android.R.string.no, null)
.setPositiveButton(R.string.show_help) { _, _ ->
openHelpLink()
}
.setOnDismissListener {
dismissCallback?.invoke()
}
.show()
}
fun openHelpLink() {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://neoterm.gitbooks.io/neoterm-wiki/content/"))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
fun easterEgg(context: Context, message: String) {
val happyCount = NeoPreference.loadInt(NeoPreference.KEY_HAPPY_EGG, 0) + 1
NeoPreference.store(NeoPreference.KEY_HAPPY_EGG, happyCount)
val trigger = NeoPreference.VALUE_HAPPY_EGG_TRIGGER
if (happyCount == trigger / 2) {
@SuppressLint("ShowToast")
val toast = Toast.makeText(this, message, Toast.LENGTH_LONG)
toast.setGravity(Gravity.CENTER, 0, 0)
toast.show()
} else if (happyCount > trigger) {
NeoPreference.store(NeoPreference.KEY_HAPPY_EGG, 0)
context.startActivity(Intent(context, BonusActivity::class.java))
}
}
companion object {
private var app: App? = null
fun get(): App {
return app!!
}
} }
}
} }

View File

@ -1,108 +1,110 @@
package io.neoterm.backend; package io.neoterm.backend;
/** A circular byte buffer allowing one producer and one consumer thread. */ /**
* A circular byte buffer allowing one producer and one consumer thread.
*/
final class ByteQueue { final class ByteQueue {
private final byte[] mBuffer; private final byte[] mBuffer;
private int mHead; private int mHead;
private int mStoredBytes; private int mStoredBytes;
private boolean mOpen = true; private boolean mOpen = true;
public ByteQueue(int size) { public ByteQueue(int size) {
mBuffer = new byte[size]; mBuffer = new byte[size];
}
public synchronized void close() {
mOpen = false;
notify();
}
public synchronized int read(byte[] buffer, boolean block) {
while (mStoredBytes == 0 && mOpen) {
if (block) {
try {
wait();
} catch (InterruptedException e) {
// Ignore.
}
} else {
return 0;
}
}
if (!mOpen) return -1;
int totalRead = 0;
int bufferLength = mBuffer.length;
boolean wasFull = bufferLength == mStoredBytes;
int length = buffer.length;
int offset = 0;
while (length > 0 && mStoredBytes > 0) {
int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
int bytesToCopy = Math.min(length, oneRun);
System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
mHead += bytesToCopy;
if (mHead >= bufferLength) mHead = 0;
mStoredBytes -= bytesToCopy;
length -= bytesToCopy;
offset += bytesToCopy;
totalRead += bytesToCopy;
}
if (wasFull) notify();
return totalRead;
}
/**
* Attempt to write the specified portion of the provided buffer to the queue.
* <p/>
* Returns whether the output was totally written, false if it was closed before.
*/
public boolean write(byte[] buffer, int offset, int lengthToWrite) {
if (lengthToWrite + offset > buffer.length) {
throw new IllegalArgumentException("length + offset > buffer.length");
} else if (lengthToWrite <= 0) {
throw new IllegalArgumentException("length <= 0");
} }
public synchronized void close() { final int bufferLength = mBuffer.length;
mOpen = false;
notify(); synchronized (this) {
} while (lengthToWrite > 0) {
while (bufferLength == mStoredBytes && mOpen) {
public synchronized int read(byte[] buffer, boolean block) { try {
while (mStoredBytes == 0 && mOpen) { wait();
if (block) { } catch (InterruptedException e) {
try { // Ignore.
wait(); }
} catch (InterruptedException e) { }
// Ignore. if (!mOpen) return false;
} final boolean wasEmpty = mStoredBytes == 0;
} else { int bytesToWriteBeforeWaiting = Math.min(lengthToWrite, bufferLength - mStoredBytes);
return 0; lengthToWrite -= bytesToWriteBeforeWaiting;
}
} while (bytesToWriteBeforeWaiting > 0) {
if (!mOpen) return -1; int tail = mHead + mStoredBytes;
int oneRun;
int totalRead = 0; if (tail >= bufferLength) {
int bufferLength = mBuffer.length; // Buffer: [.............]
boolean wasFull = bufferLength == mStoredBytes; // ________________H_______T
int length = buffer.length; // =>
int offset = 0; // Buffer: [.............]
while (length > 0 && mStoredBytes > 0) { // ___________T____H
int oneRun = Math.min(bufferLength - mHead, mStoredBytes); // onRun= _____----_
int bytesToCopy = Math.min(length, oneRun); tail = tail - bufferLength;
System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy); oneRun = mHead - tail;
mHead += bytesToCopy; } else {
if (mHead >= bufferLength) mHead = 0; oneRun = bufferLength - tail;
mStoredBytes -= bytesToCopy; }
length -= bytesToCopy; int bytesToCopy = Math.min(oneRun, bytesToWriteBeforeWaiting);
offset += bytesToCopy; System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
totalRead += bytesToCopy; offset += bytesToCopy;
} bytesToWriteBeforeWaiting -= bytesToCopy;
if (wasFull) notify(); mStoredBytes += bytesToCopy;
return totalRead; }
} if (wasEmpty) notify();
}
/**
* Attempt to write the specified portion of the provided buffer to the queue.
* <p/>
* Returns whether the output was totally written, false if it was closed before.
*/
public boolean write(byte[] buffer, int offset, int lengthToWrite) {
if (lengthToWrite + offset > buffer.length) {
throw new IllegalArgumentException("length + offset > buffer.length");
} else if (lengthToWrite <= 0) {
throw new IllegalArgumentException("length <= 0");
}
final int bufferLength = mBuffer.length;
synchronized (this) {
while (lengthToWrite > 0) {
while (bufferLength == mStoredBytes && mOpen) {
try {
wait();
} catch (InterruptedException e) {
// Ignore.
}
}
if (!mOpen) return false;
final boolean wasEmpty = mStoredBytes == 0;
int bytesToWriteBeforeWaiting = Math.min(lengthToWrite, bufferLength - mStoredBytes);
lengthToWrite -= bytesToWriteBeforeWaiting;
while (bytesToWriteBeforeWaiting > 0) {
int tail = mHead + mStoredBytes;
int oneRun;
if (tail >= bufferLength) {
// Buffer: [.............]
// ________________H_______T
// =>
// Buffer: [.............]
// ___________T____H
// onRun= _____----_
tail = tail - bufferLength;
oneRun = mHead - tail;
} else {
oneRun = bufferLength - tail;
}
int bytesToCopy = Math.min(oneRun, bytesToWriteBeforeWaiting);
System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
offset += bytesToCopy;
bytesToWriteBeforeWaiting -= bytesToCopy;
mStoredBytes += bytesToCopy;
}
if (wasEmpty) notify();
}
}
return true;
} }
return true;
}
} }

View File

@ -4,7 +4,9 @@ import android.util.Log;
public final class EmulatorDebug { public final class EmulatorDebug {
/** The tag to use with {@link Log}. */ /**
public static final String LOG_TAG = "NeoTerm-Emulator"; * The tag to use with {@link Log}.
*/
public static final String LOG_TAG = "NeoTerm-Emulator";
} }

View File

@ -5,37 +5,41 @@ package io.neoterm.backend;
*/ */
final class JNI { final class JNI {
static { static {
System.loadLibrary("neoterm"); System.loadLibrary("neoterm");
} }
/** /**
* Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the * Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the
* subprocess. * subprocess.
* <p/> * <p/>
* Callers are responsible for calling {@link #close(int)} on the returned file descriptor. * Callers are responsible for calling {@link #close(int)} on the returned file descriptor.
* *
* @param cmd The command to execute * @param cmd The command to execute
* @param cwd The current working directory for the executed command * @param cwd The current working directory for the executed command
* @param args An array of arguments to the command * @param args An array of arguments to the command
* @param envVars An array of strings of the form "VAR=value" to be added to the environment of the process * @param envVars An array of strings of the form "VAR=value" to be added to the environment of the process
* @param processId A one-element array to which the process ID of the started process will be written. * @param processId A one-element array to which the process ID of the started process will be written.
* @return the file descriptor resulting from opening /dev/ptmx master device. The sub process will have opened the * @return the file descriptor resulting from opening /dev/ptmx master device. The sub process will have opened the
* slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr. * slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr.
*/ */
public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns); public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns);
/** Set the window size for a given pty, which allows connected programs to learn how large their screen is. */ /**
public static native void setPtyWindowSize(int fd, int rows, int cols); * Set the window size for a given pty, which allows connected programs to learn how large their screen is.
*/
public static native void setPtyWindowSize(int fd, int rows, int cols);
/** /**
* Causes the calling thread to wait for the process associated with the receiver to finish executing. * Causes the calling thread to wait for the process associated with the receiver to finish executing.
* *
* @return if >= 0, the exit status of the process. If < 0, the signal causing the process to stop negated. * @return if >= 0, the exit status of the process. If < 0, the signal causing the process to stop negated.
*/ */
public static native int waitFor(int processId); public static native int waitFor(int processId);
/** Close a file descriptor through the close(2) system call. */ /**
public static native void close(int fileDescriptor); * Close a file descriptor through the close(2) system call.
*/
public static native void close(int fileDescriptor);
} }

View File

@ -3,311 +3,262 @@ package io.neoterm.backend;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static android.view.KeyEvent.KEYCODE_BACK; import static android.view.KeyEvent.*;
import static android.view.KeyEvent.KEYCODE_BREAK;
import static android.view.KeyEvent.KEYCODE_DEL;
import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.KeyEvent.KEYCODE_ESCAPE;
import static android.view.KeyEvent.KEYCODE_F1;
import static android.view.KeyEvent.KEYCODE_F10;
import static android.view.KeyEvent.KEYCODE_F11;
import static android.view.KeyEvent.KEYCODE_F12;
import static android.view.KeyEvent.KEYCODE_F2;
import static android.view.KeyEvent.KEYCODE_F3;
import static android.view.KeyEvent.KEYCODE_F4;
import static android.view.KeyEvent.KEYCODE_F5;
import static android.view.KeyEvent.KEYCODE_F6;
import static android.view.KeyEvent.KEYCODE_F7;
import static android.view.KeyEvent.KEYCODE_F8;
import static android.view.KeyEvent.KEYCODE_F9;
import static android.view.KeyEvent.KEYCODE_FORWARD_DEL;
import static android.view.KeyEvent.KEYCODE_INSERT;
import static android.view.KeyEvent.KEYCODE_MOVE_END;
import static android.view.KeyEvent.KEYCODE_MOVE_HOME;
import static android.view.KeyEvent.KEYCODE_NUMPAD_0;
import static android.view.KeyEvent.KEYCODE_NUMPAD_1;
import static android.view.KeyEvent.KEYCODE_NUMPAD_2;
import static android.view.KeyEvent.KEYCODE_NUMPAD_3;
import static android.view.KeyEvent.KEYCODE_NUMPAD_4;
import static android.view.KeyEvent.KEYCODE_NUMPAD_5;
import static android.view.KeyEvent.KEYCODE_NUMPAD_6;
import static android.view.KeyEvent.KEYCODE_NUMPAD_7;
import static android.view.KeyEvent.KEYCODE_NUMPAD_8;
import static android.view.KeyEvent.KEYCODE_NUMPAD_9;
import static android.view.KeyEvent.KEYCODE_NUMPAD_ADD;
import static android.view.KeyEvent.KEYCODE_NUMPAD_COMMA;
import static android.view.KeyEvent.KEYCODE_NUMPAD_DIVIDE;
import static android.view.KeyEvent.KEYCODE_NUMPAD_DOT;
import static android.view.KeyEvent.KEYCODE_NUMPAD_ENTER;
import static android.view.KeyEvent.KEYCODE_NUMPAD_EQUALS;
import static android.view.KeyEvent.KEYCODE_NUMPAD_MULTIPLY;
import static android.view.KeyEvent.KEYCODE_NUMPAD_SUBTRACT;
import static android.view.KeyEvent.KEYCODE_NUM_LOCK;
import static android.view.KeyEvent.KEYCODE_PAGE_DOWN;
import static android.view.KeyEvent.KEYCODE_PAGE_UP;
import static android.view.KeyEvent.KEYCODE_SPACE;
import static android.view.KeyEvent.KEYCODE_SYSRQ;
import static android.view.KeyEvent.KEYCODE_TAB;
public final class KeyHandler { public final class KeyHandler {
public static final int KEYMOD_ALT = 0x80000000; public static final int KEYMOD_ALT = 0x80000000;
public static final int KEYMOD_CTRL = 0x40000000; public static final int KEYMOD_CTRL = 0x40000000;
public static final int KEYMOD_SHIFT = 0x20000000; public static final int KEYMOD_SHIFT = 0x20000000;
private static final Map<String, Integer> TERMCAP_TO_KEYCODE = new HashMap<>(); private static final Map<String, Integer> TERMCAP_TO_KEYCODE = new HashMap<>();
static { static {
// terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html // terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html
// termcap: http://man7.org/linux/man-pages/man5/termcap.5.html // termcap: http://man7.org/linux/man-pages/man5/termcap.5.html
TERMCAP_TO_KEYCODE.put("%i", KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT); TERMCAP_TO_KEYCODE.put("%i", KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT);
TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_MOVE_HOME); // Shifted home TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_MOVE_HOME); // Shifted home
TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT); TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT);
TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key
TERMCAP_TO_KEYCODE.put("k1", KEYCODE_F1); TERMCAP_TO_KEYCODE.put("k1", KEYCODE_F1);
TERMCAP_TO_KEYCODE.put("k2", KEYCODE_F2); TERMCAP_TO_KEYCODE.put("k2", KEYCODE_F2);
TERMCAP_TO_KEYCODE.put("k3", KEYCODE_F3); TERMCAP_TO_KEYCODE.put("k3", KEYCODE_F3);
TERMCAP_TO_KEYCODE.put("k4", KEYCODE_F4); TERMCAP_TO_KEYCODE.put("k4", KEYCODE_F4);
TERMCAP_TO_KEYCODE.put("k5", KEYCODE_F5); TERMCAP_TO_KEYCODE.put("k5", KEYCODE_F5);
TERMCAP_TO_KEYCODE.put("k6", KEYCODE_F6); TERMCAP_TO_KEYCODE.put("k6", KEYCODE_F6);
TERMCAP_TO_KEYCODE.put("k7", KEYCODE_F7); TERMCAP_TO_KEYCODE.put("k7", KEYCODE_F7);
TERMCAP_TO_KEYCODE.put("k8", KEYCODE_F8); TERMCAP_TO_KEYCODE.put("k8", KEYCODE_F8);
TERMCAP_TO_KEYCODE.put("k9", KEYCODE_F9); TERMCAP_TO_KEYCODE.put("k9", KEYCODE_F9);
TERMCAP_TO_KEYCODE.put("k;", KEYCODE_F10); TERMCAP_TO_KEYCODE.put("k;", KEYCODE_F10);
TERMCAP_TO_KEYCODE.put("F1", KEYCODE_F11); TERMCAP_TO_KEYCODE.put("F1", KEYCODE_F11);
TERMCAP_TO_KEYCODE.put("F2", KEYCODE_F12); TERMCAP_TO_KEYCODE.put("F2", KEYCODE_F12);
TERMCAP_TO_KEYCODE.put("F3", KEYMOD_SHIFT | KEYCODE_F1); TERMCAP_TO_KEYCODE.put("F3", KEYMOD_SHIFT | KEYCODE_F1);
TERMCAP_TO_KEYCODE.put("F4", KEYMOD_SHIFT | KEYCODE_F2); TERMCAP_TO_KEYCODE.put("F4", KEYMOD_SHIFT | KEYCODE_F2);
TERMCAP_TO_KEYCODE.put("F5", KEYMOD_SHIFT | KEYCODE_F3); TERMCAP_TO_KEYCODE.put("F5", KEYMOD_SHIFT | KEYCODE_F3);
TERMCAP_TO_KEYCODE.put("F6", KEYMOD_SHIFT | KEYCODE_F4); TERMCAP_TO_KEYCODE.put("F6", KEYMOD_SHIFT | KEYCODE_F4);
TERMCAP_TO_KEYCODE.put("F7", KEYMOD_SHIFT | KEYCODE_F5); TERMCAP_TO_KEYCODE.put("F7", KEYMOD_SHIFT | KEYCODE_F5);
TERMCAP_TO_KEYCODE.put("F8", KEYMOD_SHIFT | KEYCODE_F6); TERMCAP_TO_KEYCODE.put("F8", KEYMOD_SHIFT | KEYCODE_F6);
TERMCAP_TO_KEYCODE.put("F9", KEYMOD_SHIFT | KEYCODE_F7); TERMCAP_TO_KEYCODE.put("F9", KEYMOD_SHIFT | KEYCODE_F7);
TERMCAP_TO_KEYCODE.put("FA", KEYMOD_SHIFT | KEYCODE_F8); TERMCAP_TO_KEYCODE.put("FA", KEYMOD_SHIFT | KEYCODE_F8);
TERMCAP_TO_KEYCODE.put("FB", KEYMOD_SHIFT | KEYCODE_F9); TERMCAP_TO_KEYCODE.put("FB", KEYMOD_SHIFT | KEYCODE_F9);
TERMCAP_TO_KEYCODE.put("FC", KEYMOD_SHIFT | KEYCODE_F10); TERMCAP_TO_KEYCODE.put("FC", KEYMOD_SHIFT | KEYCODE_F10);
TERMCAP_TO_KEYCODE.put("FD", KEYMOD_SHIFT | KEYCODE_F11); TERMCAP_TO_KEYCODE.put("FD", KEYMOD_SHIFT | KEYCODE_F11);
TERMCAP_TO_KEYCODE.put("FE", KEYMOD_SHIFT | KEYCODE_F12); TERMCAP_TO_KEYCODE.put("FE", KEYMOD_SHIFT | KEYCODE_F12);
TERMCAP_TO_KEYCODE.put("kb", KEYCODE_DEL); // backspace key TERMCAP_TO_KEYCODE.put("kb", KEYCODE_DEL); // backspace key
TERMCAP_TO_KEYCODE.put("kd", KEYCODE_DPAD_DOWN); // terminfo=kcud1, down-arrow key TERMCAP_TO_KEYCODE.put("kd", KEYCODE_DPAD_DOWN); // terminfo=kcud1, down-arrow key
TERMCAP_TO_KEYCODE.put("kh", KEYCODE_MOVE_HOME); TERMCAP_TO_KEYCODE.put("kh", KEYCODE_MOVE_HOME);
TERMCAP_TO_KEYCODE.put("kl", KEYCODE_DPAD_LEFT); TERMCAP_TO_KEYCODE.put("kl", KEYCODE_DPAD_LEFT);
TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT); TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT);
// K1=Upper left of keypad: // K1=Upper left of keypad:
// t_K1 <kHome> keypad home key // t_K1 <kHome> keypad home key
// t_K3 <kPageUp> keypad page-up key // t_K3 <kPageUp> keypad page-up key
// t_K4 <kEnd> keypad end key // t_K4 <kEnd> keypad end key
// t_K5 <kPageDown> keypad page-down key // t_K5 <kPageDown> keypad page-down key
TERMCAP_TO_KEYCODE.put("K1", KEYCODE_MOVE_HOME); TERMCAP_TO_KEYCODE.put("K1", KEYCODE_MOVE_HOME);
TERMCAP_TO_KEYCODE.put("K3", KEYCODE_PAGE_UP); TERMCAP_TO_KEYCODE.put("K3", KEYCODE_PAGE_UP);
TERMCAP_TO_KEYCODE.put("K4", KEYCODE_MOVE_END); TERMCAP_TO_KEYCODE.put("K4", KEYCODE_MOVE_END);
TERMCAP_TO_KEYCODE.put("K5", KEYCODE_PAGE_DOWN); TERMCAP_TO_KEYCODE.put("K5", KEYCODE_PAGE_DOWN);
TERMCAP_TO_KEYCODE.put("ku", KEYCODE_DPAD_UP); TERMCAP_TO_KEYCODE.put("ku", KEYCODE_DPAD_UP);
TERMCAP_TO_KEYCODE.put("kB", KEYMOD_SHIFT | KEYCODE_TAB); // termcap=kB, terminfo=kcbt: Back-tab_switcher TERMCAP_TO_KEYCODE.put("kB", KEYMOD_SHIFT | KEYCODE_TAB); // termcap=kB, terminfo=kcbt: Back-tab_switcher
TERMCAP_TO_KEYCODE.put("kD", KEYCODE_FORWARD_DEL); // terminfo=kdch1, delete-character key TERMCAP_TO_KEYCODE.put("kD", KEYCODE_FORWARD_DEL); // terminfo=kdch1, delete-character key
TERMCAP_TO_KEYCODE.put("kDN", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // non-standard shifted arrow down TERMCAP_TO_KEYCODE.put("kDN", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // non-standard shifted arrow down
TERMCAP_TO_KEYCODE.put("kF", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // terminfo=kind, scroll-forward key TERMCAP_TO_KEYCODE.put("kF", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // terminfo=kind, scroll-forward key
TERMCAP_TO_KEYCODE.put("kI", KEYCODE_INSERT); TERMCAP_TO_KEYCODE.put("kI", KEYCODE_INSERT);
TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_UP); TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_UP);
TERMCAP_TO_KEYCODE.put("kP", KEYCODE_PAGE_DOWN); TERMCAP_TO_KEYCODE.put("kP", KEYCODE_PAGE_DOWN);
TERMCAP_TO_KEYCODE.put("kR", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // terminfo=kri, scroll-backward key TERMCAP_TO_KEYCODE.put("kR", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // terminfo=kri, scroll-backward key
TERMCAP_TO_KEYCODE.put("kUP", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // non-standard shifted up TERMCAP_TO_KEYCODE.put("kUP", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // non-standard shifted up
TERMCAP_TO_KEYCODE.put("@7", KEYCODE_MOVE_END); TERMCAP_TO_KEYCODE.put("@7", KEYCODE_MOVE_END);
TERMCAP_TO_KEYCODE.put("@8", KEYCODE_NUMPAD_ENTER); TERMCAP_TO_KEYCODE.put("@8", KEYCODE_NUMPAD_ENTER);
}
static String getCodeFromTermcap(String termcap, boolean cursorKeysApplication, boolean keypadApplication) {
Integer keyCodeAndMod = TERMCAP_TO_KEYCODE.get(termcap);
if (keyCodeAndMod == null) return null;
int keyCode = keyCodeAndMod;
int keyMod = 0;
if ((keyCode & KEYMOD_SHIFT) != 0) {
keyMod |= KEYMOD_SHIFT;
keyCode &= ~KEYMOD_SHIFT;
}
if ((keyCode & KEYMOD_CTRL) != 0) {
keyMod |= KEYMOD_CTRL;
keyCode &= ~KEYMOD_CTRL;
}
if ((keyCode & KEYMOD_ALT) != 0) {
keyMod |= KEYMOD_ALT;
keyCode &= ~KEYMOD_ALT;
}
return getCode(keyCode, keyMod, cursorKeysApplication, keypadApplication);
}
public static String getCode(int keyCode, int keyMode, boolean cursorApp, boolean keypadApplication) {
switch (keyCode) {
case KEYCODE_DPAD_CENTER:
return "\015";
case KEYCODE_DPAD_UP:
return (keyMode == 0) ? (cursorApp ? "\033OA" : "\033[A") : transformForModifiers("\033[1", keyMode, 'A');
case KEYCODE_DPAD_DOWN:
return (keyMode == 0) ? (cursorApp ? "\033OB" : "\033[B") : transformForModifiers("\033[1", keyMode, 'B');
case KEYCODE_DPAD_RIGHT:
return (keyMode == 0) ? (cursorApp ? "\033OC" : "\033[C") : transformForModifiers("\033[1", keyMode, 'C');
case KEYCODE_DPAD_LEFT:
return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D');
case KEYCODE_MOVE_HOME:
// Note that KEYCODE_HOME is handled by the system and never delivered to applications.
// On a Logitech k810 keyboard KEYCODE_MOVE_HOME is sent by FN+LeftArrow.
return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H');
case KEYCODE_MOVE_END:
return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F');
// An xterm can send function keys F1 to F4 in two modes: vt100 compatible or
// not. Because Vim may not know what the xterm is sending, both types of keys
// are recognized. The same happens for the <Home> and <End> keys.
// normal vt100 ~
// <F1> t_k1 <Esc>[11~ <xF1> <Esc>OP *<xF1>-xterm*
// <F2> t_k2 <Esc>[12~ <xF2> <Esc>OQ *<xF2>-xterm*
// <F3> t_k3 <Esc>[13~ <xF3> <Esc>OR *<xF3>-xterm*
// <F4> t_k4 <Esc>[14~ <xF4> <Esc>OS *<xF4>-xterm*
// <Home> t_kh <Esc>[7~ <xHome> <Esc>OH *<xHome>-xterm*
// <End> t_@7 <Esc>[4~ <xEnd> <Esc>OF *<xEnd>-xterm*
case KEYCODE_F1:
return (keyMode == 0) ? "\033OP" : transformForModifiers("\033[1", keyMode, 'P');
case KEYCODE_F2:
return (keyMode == 0) ? "\033OQ" : transformForModifiers("\033[1", keyMode, 'Q');
case KEYCODE_F3:
return (keyMode == 0) ? "\033OR" : transformForModifiers("\033[1", keyMode, 'R');
case KEYCODE_F4:
return (keyMode == 0) ? "\033OS" : transformForModifiers("\033[1", keyMode, 'S');
case KEYCODE_F5:
return transformForModifiers("\033[15", keyMode, '~');
case KEYCODE_F6:
return transformForModifiers("\033[17", keyMode, '~');
case KEYCODE_F7:
return transformForModifiers("\033[18", keyMode, '~');
case KEYCODE_F8:
return transformForModifiers("\033[19", keyMode, '~');
case KEYCODE_F9:
return transformForModifiers("\033[20", keyMode, '~');
case KEYCODE_F10:
return transformForModifiers("\033[21", keyMode, '~');
case KEYCODE_F11:
return transformForModifiers("\033[23", keyMode, '~');
case KEYCODE_F12:
return transformForModifiers("\033[24", keyMode, '~');
case KEYCODE_SYSRQ:
return "\033[32~"; // Sys Request / Print
// Is this Scroll lock? case Cancel: return "\033[33~";
case KEYCODE_BREAK:
return "\033[34~"; // Pause/Break
case KEYCODE_ESCAPE:
case KEYCODE_BACK:
return "\033";
case KEYCODE_INSERT:
return transformForModifiers("\033[2", keyMode, '~');
case KEYCODE_FORWARD_DEL:
return transformForModifiers("\033[3", keyMode, '~');
case KEYCODE_PAGE_UP:
return "\033[5~";
case KEYCODE_PAGE_DOWN:
return "\033[6~";
case KEYCODE_DEL:
String prefix = ((keyMode & KEYMOD_ALT) == 0) ? "" : "\033";
// Just do what xterm and gnome-terminal does:
return prefix + (((keyMode & KEYMOD_CTRL) == 0) ? "\u007F" : "\u0008");
case KEYCODE_NUM_LOCK:
return "\033OP";
case KEYCODE_SPACE:
// If ctrl is not down, return null so that it goes through normal input processing (which may e.g. cause a
// combining accent to be written):
return ((keyMode & KEYMOD_CTRL) == 0) ? null : "\0";
case KEYCODE_TAB:
// This is back-tab_switcher when shifted:
return (keyMode & KEYMOD_SHIFT) == 0 ? "\011" : "\033[Z";
case KEYCODE_ENTER:
return ((keyMode & KEYMOD_ALT) == 0) ? "\r" : "\033\r";
case KEYCODE_NUMPAD_ENTER:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'M') : "\n";
case KEYCODE_NUMPAD_MULTIPLY:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'j') : "*";
case KEYCODE_NUMPAD_ADD:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+";
case KEYCODE_NUMPAD_COMMA:
return ",";
case KEYCODE_NUMPAD_DOT:
return keypadApplication ? "\033On" : ".";
case KEYCODE_NUMPAD_SUBTRACT:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
case KEYCODE_NUMPAD_DIVIDE:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
case KEYCODE_NUMPAD_0:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "0";
case KEYCODE_NUMPAD_1:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
case KEYCODE_NUMPAD_2:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'r') : "2";
case KEYCODE_NUMPAD_3:
return keypadApplication ? transformForModifiers("\033O", keyMode, 's') : "3";
case KEYCODE_NUMPAD_4:
return keypadApplication ? transformForModifiers("\033O", keyMode, 't') : "4";
case KEYCODE_NUMPAD_5:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'u') : "5";
case KEYCODE_NUMPAD_6:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'v') : "6";
case KEYCODE_NUMPAD_7:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'w') : "7";
case KEYCODE_NUMPAD_8:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'x') : "8";
case KEYCODE_NUMPAD_9:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'y') : "9";
case KEYCODE_NUMPAD_EQUALS:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'X') : "=";
} }
static String getCodeFromTermcap(String termcap, boolean cursorKeysApplication, boolean keypadApplication) { return null;
Integer keyCodeAndMod = TERMCAP_TO_KEYCODE.get(termcap); }
if (keyCodeAndMod == null) return null;
int keyCode = keyCodeAndMod; private static String transformForModifiers(String start, int keymod, char lastChar) {
int keyMod = 0; int modifier;
if ((keyCode & KEYMOD_SHIFT) != 0) { switch (keymod) {
keyMod |= KEYMOD_SHIFT; case KEYMOD_SHIFT:
keyCode &= ~KEYMOD_SHIFT; modifier = 2;
} break;
if ((keyCode & KEYMOD_CTRL) != 0) { case KEYMOD_ALT:
keyMod |= KEYMOD_CTRL; modifier = 3;
keyCode &= ~KEYMOD_CTRL; break;
} case (KEYMOD_SHIFT | KEYMOD_ALT):
if ((keyCode & KEYMOD_ALT) != 0) { modifier = 4;
keyMod |= KEYMOD_ALT; break;
keyCode &= ~KEYMOD_ALT; case KEYMOD_CTRL:
} modifier = 5;
return getCode(keyCode, keyMod, cursorKeysApplication, keypadApplication); break;
} case KEYMOD_SHIFT | KEYMOD_CTRL:
modifier = 6;
public static String getCode(int keyCode, int keyMode, boolean cursorApp, boolean keypadApplication) { break;
switch (keyCode) { case KEYMOD_ALT | KEYMOD_CTRL:
case KEYCODE_DPAD_CENTER: modifier = 7;
return "\015"; break;
case KEYMOD_SHIFT | KEYMOD_ALT | KEYMOD_CTRL:
case KEYCODE_DPAD_UP: modifier = 8;
return (keyMode == 0) ? (cursorApp ? "\033OA" : "\033[A") : transformForModifiers("\033[1", keyMode, 'A'); break;
case KEYCODE_DPAD_DOWN: default:
return (keyMode == 0) ? (cursorApp ? "\033OB" : "\033[B") : transformForModifiers("\033[1", keyMode, 'B'); return start + lastChar;
case KEYCODE_DPAD_RIGHT:
return (keyMode == 0) ? (cursorApp ? "\033OC" : "\033[C") : transformForModifiers("\033[1", keyMode, 'C');
case KEYCODE_DPAD_LEFT:
return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D');
case KEYCODE_MOVE_HOME:
// Note that KEYCODE_HOME is handled by the system and never delivered to applications.
// On a Logitech k810 keyboard KEYCODE_MOVE_HOME is sent by FN+LeftArrow.
return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H');
case KEYCODE_MOVE_END:
return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F');
// An xterm can send function keys F1 to F4 in two modes: vt100 compatible or
// not. Because Vim may not know what the xterm is sending, both types of keys
// are recognized. The same happens for the <Home> and <End> keys.
// normal vt100 ~
// <F1> t_k1 <Esc>[11~ <xF1> <Esc>OP *<xF1>-xterm*
// <F2> t_k2 <Esc>[12~ <xF2> <Esc>OQ *<xF2>-xterm*
// <F3> t_k3 <Esc>[13~ <xF3> <Esc>OR *<xF3>-xterm*
// <F4> t_k4 <Esc>[14~ <xF4> <Esc>OS *<xF4>-xterm*
// <Home> t_kh <Esc>[7~ <xHome> <Esc>OH *<xHome>-xterm*
// <End> t_@7 <Esc>[4~ <xEnd> <Esc>OF *<xEnd>-xterm*
case KEYCODE_F1:
return (keyMode == 0) ? "\033OP" : transformForModifiers("\033[1", keyMode, 'P');
case KEYCODE_F2:
return (keyMode == 0) ? "\033OQ" : transformForModifiers("\033[1", keyMode, 'Q');
case KEYCODE_F3:
return (keyMode == 0) ? "\033OR" : transformForModifiers("\033[1", keyMode, 'R');
case KEYCODE_F4:
return (keyMode == 0) ? "\033OS" : transformForModifiers("\033[1", keyMode, 'S');
case KEYCODE_F5:
return transformForModifiers("\033[15", keyMode, '~');
case KEYCODE_F6:
return transformForModifiers("\033[17", keyMode, '~');
case KEYCODE_F7:
return transformForModifiers("\033[18", keyMode, '~');
case KEYCODE_F8:
return transformForModifiers("\033[19", keyMode, '~');
case KEYCODE_F9:
return transformForModifiers("\033[20", keyMode, '~');
case KEYCODE_F10:
return transformForModifiers("\033[21", keyMode, '~');
case KEYCODE_F11:
return transformForModifiers("\033[23", keyMode, '~');
case KEYCODE_F12:
return transformForModifiers("\033[24", keyMode, '~');
case KEYCODE_SYSRQ:
return "\033[32~"; // Sys Request / Print
// Is this Scroll lock? case Cancel: return "\033[33~";
case KEYCODE_BREAK:
return "\033[34~"; // Pause/Break
case KEYCODE_ESCAPE:
case KEYCODE_BACK:
return "\033";
case KEYCODE_INSERT:
return transformForModifiers("\033[2", keyMode, '~');
case KEYCODE_FORWARD_DEL:
return transformForModifiers("\033[3", keyMode, '~');
case KEYCODE_PAGE_UP:
return "\033[5~";
case KEYCODE_PAGE_DOWN:
return "\033[6~";
case KEYCODE_DEL:
String prefix = ((keyMode & KEYMOD_ALT) == 0) ? "" : "\033";
// Just do what xterm and gnome-terminal does:
return prefix + (((keyMode & KEYMOD_CTRL) == 0) ? "\u007F" : "\u0008");
case KEYCODE_NUM_LOCK:
return "\033OP";
case KEYCODE_SPACE:
// If ctrl is not down, return null so that it goes through normal input processing (which may e.g. cause a
// combining accent to be written):
return ((keyMode & KEYMOD_CTRL) == 0) ? null : "\0";
case KEYCODE_TAB:
// This is back-tab_switcher when shifted:
return (keyMode & KEYMOD_SHIFT) == 0 ? "\011" : "\033[Z";
case KEYCODE_ENTER:
return ((keyMode & KEYMOD_ALT) == 0) ? "\r" : "\033\r";
case KEYCODE_NUMPAD_ENTER:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'M') : "\n";
case KEYCODE_NUMPAD_MULTIPLY:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'j') : "*";
case KEYCODE_NUMPAD_ADD:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+";
case KEYCODE_NUMPAD_COMMA:
return ",";
case KEYCODE_NUMPAD_DOT:
return keypadApplication ? "\033On" : ".";
case KEYCODE_NUMPAD_SUBTRACT:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
case KEYCODE_NUMPAD_DIVIDE:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
case KEYCODE_NUMPAD_0:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "0";
case KEYCODE_NUMPAD_1:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
case KEYCODE_NUMPAD_2:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'r') : "2";
case KEYCODE_NUMPAD_3:
return keypadApplication ? transformForModifiers("\033O", keyMode, 's') : "3";
case KEYCODE_NUMPAD_4:
return keypadApplication ? transformForModifiers("\033O", keyMode, 't') : "4";
case KEYCODE_NUMPAD_5:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'u') : "5";
case KEYCODE_NUMPAD_6:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'v') : "6";
case KEYCODE_NUMPAD_7:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'w') : "7";
case KEYCODE_NUMPAD_8:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'x') : "8";
case KEYCODE_NUMPAD_9:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'y') : "9";
case KEYCODE_NUMPAD_EQUALS:
return keypadApplication ? transformForModifiers("\033O", keyMode, 'X') : "=";
}
return null;
}
private static String transformForModifiers(String start, int keymod, char lastChar) {
int modifier;
switch (keymod) {
case KEYMOD_SHIFT:
modifier = 2;
break;
case KEYMOD_ALT:
modifier = 3;
break;
case (KEYMOD_SHIFT | KEYMOD_ALT):
modifier = 4;
break;
case KEYMOD_CTRL:
modifier = 5;
break;
case KEYMOD_SHIFT | KEYMOD_CTRL:
modifier = 6;
break;
case KEYMOD_ALT | KEYMOD_CTRL:
modifier = 7;
break;
case KEYMOD_SHIFT | KEYMOD_ALT | KEYMOD_CTRL:
modifier = 8;
break;
default:
return start + lastChar;
}
return start + (";" + modifier) + lastChar;
} }
return start + (";" + modifier) + lastChar;
}
} }

View File

@ -8,418 +8,428 @@ package io.neoterm.backend;
*/ */
public final class TerminalBuffer { public final class TerminalBuffer {
TerminalRow[] mLines; TerminalRow[] mLines;
/** The length of {@link #mLines}. */ /**
int mTotalRows; * The length of {@link #mLines}.
/** The number of rows and columns visible on the screen. */ */
int mScreenRows, mColumns; int mTotalRows;
/** The number of rows kept in history. */ /**
private int mActiveTranscriptRows = 0; * The number of rows and columns visible on the screen.
/** The index in the circular buffer where the visible screen starts. */ */
private int mScreenFirstRow = 0; int mScreenRows, mColumns;
/**
* The number of rows kept in history.
*/
private int mActiveTranscriptRows = 0;
/**
* The index in the circular buffer where the visible screen starts.
*/
private int mScreenFirstRow = 0;
/** /**
* Create a transcript screen. * Create a transcript screen.
* *
* @param columns the width of the screen in characters. * @param columns the width of the screen in characters.
* @param totalRows the height of the entire text area, in rows of text. * @param totalRows the height of the entire text area, in rows of text.
* @param screenRows the height of just the screen, not including the transcript that holds lines that have scrolled off * @param screenRows the height of just the screen, not including the transcript that holds lines that have scrolled off
* the top of the screen. * the top of the screen.
*/ */
public TerminalBuffer(int columns, int totalRows, int screenRows) { public TerminalBuffer(int columns, int totalRows, int screenRows) {
mColumns = columns; mColumns = columns;
mTotalRows = totalRows; mTotalRows = totalRows;
mScreenRows = screenRows; mScreenRows = screenRows;
mLines = new TerminalRow[totalRows]; mLines = new TerminalRow[totalRows];
blockSet(0, 0, columns, screenRows, ' ', TextStyle.NORMAL); blockSet(0, 0, columns, screenRows, ' ', TextStyle.NORMAL);
}
public String getTranscriptText() {
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim();
}
public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
final StringBuilder builder = new StringBuilder();
final int columns = mColumns;
if (selY1 < -getActiveTranscriptRows()) selY1 = -getActiveTranscriptRows();
if (selY2 >= mScreenRows) selY2 = mScreenRows - 1;
for (int row = selY1; row <= selY2; row++) {
int x1 = (row == selY1) ? selX1 : 0;
int x2;
if (row == selY2) {
x2 = selX2 + 1;
if (x2 > columns) x2 = columns;
} else {
x2 = columns;
}
TerminalRow lineObject = mLines[externalToInternalRow(row)];
int x1Index = lineObject.findStartOfColumn(x1);
int x2Index = (x2 < mColumns) ? lineObject.findStartOfColumn(x2) : lineObject.getSpaceUsed();
if (x2Index == x1Index) {
// Selected the start of a wide character.
x2Index = lineObject.findStartOfColumn(x2 + 1);
}
char[] line = lineObject.mText;
int lastPrintingCharIndex = -1;
int i;
boolean rowLineWrap = getLineWrap(row);
if (rowLineWrap && x2 == columns) {
// If the line was wrapped, we shouldn't lose trailing space:
lastPrintingCharIndex = x2Index - 1;
} else {
for (i = x1Index; i < x2Index; ++i) {
char c = line[i];
if (c != ' ') lastPrintingCharIndex = i;
}
}
if (lastPrintingCharIndex != -1)
builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n');
} }
return builder.toString();
}
public String getTranscriptText() { public int getActiveTranscriptRows() {
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim(); return mActiveTranscriptRows;
} }
public String getSelectedText(int selX1, int selY1, int selX2, int selY2) { public int getActiveRows() {
final StringBuilder builder = new StringBuilder(); return mActiveTranscriptRows + mScreenRows;
final int columns = mColumns; }
if (selY1 < -getActiveTranscriptRows()) selY1 = -getActiveTranscriptRows(); /**
if (selY2 >= mScreenRows) selY2 = mScreenRows - 1; * Convert a row value from the public external coordinate system to our internal private coordinate system.
*
* <pre>
* - External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
* - Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
* mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
*
* External Internal:
*
* [ ... ] [ ... ]
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
* [ ... ] [ ... ]
* [ 0 (visible screen starts here) ] [ mScreenFirstRow ]
* [ ... ] [ ... ]
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
* </pre>
*
* @param externalRow a row in the external coordinate system.
* @return The row corresponding to the input argument in the private coordinate system.
*/
public int externalToInternalRow(int externalRow) {
if (externalRow < -mActiveTranscriptRows || externalRow > mScreenRows)
throw new IllegalArgumentException("extRow=" + externalRow + ", mScreenRows=" + mScreenRows + ", mActiveTranscriptRows=" + mActiveTranscriptRows);
final int internalRow = mScreenFirstRow + externalRow;
return (internalRow < 0) ? (mTotalRows + internalRow) : (internalRow % mTotalRows);
}
for (int row = selY1; row <= selY2; row++) { public void setLineWrap(int row) {
int x1 = (row == selY1) ? selX1 : 0; mLines[externalToInternalRow(row)].mLineWrap = true;
int x2; }
if (row == selY2) {
x2 = selX2 + 1; public boolean getLineWrap(int row) {
if (x2 > columns) x2 = columns; return mLines[externalToInternalRow(row)].mLineWrap;
}
public void clearLineWrap(int row) {
mLines[externalToInternalRow(row)].mLineWrap = false;
}
/**
* Resize the screen which this transcript backs. Currently, this only works if the number of columns does not
* change or the rows expand (that is, it only works when shrinking the number of rows).
*
* @param newColumns The number of columns the screen should have.
* @param newRows The number of rows the screen should have.
* @param cursor An int[2] containing the (column, row) cursorColor location.
*/
public void resize(int newColumns, int newRows, int newTotalRows, int[] cursor, long currentStyle, boolean altScreen) {
// newRows > mTotalRows should not normally happen since mTotalRows is TRANSCRIPT_ROWS (10000):
if (newColumns == mColumns && newRows <= mTotalRows) {
// Fast resize where just the rows changed.
int shiftDownOfTopRow = mScreenRows - newRows;
if (shiftDownOfTopRow > 0 && shiftDownOfTopRow < mScreenRows) {
// Shrinking. Check if we can skip blank rows at bottom below cursorColor.
for (int i = mScreenRows - 1; i > 0; i--) {
if (cursor[1] >= i) break;
int r = externalToInternalRow(i);
if (mLines[r] == null || mLines[r].isBlank()) {
if (--shiftDownOfTopRow == 0) break;
}
}
} else if (shiftDownOfTopRow < 0) {
// Negative shift down = expanding. Only move screen up if there is transcript to show:
int actualShift = Math.max(shiftDownOfTopRow, -mActiveTranscriptRows);
if (shiftDownOfTopRow != actualShift) {
// The new lines revealed by the resizing are not all from the transcript. Blank the below ones.
for (int i = 0; i < actualShift - shiftDownOfTopRow; i++)
allocateFullLineIfNecessary((mScreenFirstRow + mScreenRows + i) % mTotalRows).clear(currentStyle);
shiftDownOfTopRow = actualShift;
}
}
mScreenFirstRow += shiftDownOfTopRow;
mScreenFirstRow = (mScreenFirstRow < 0) ? (mScreenFirstRow + mTotalRows) : (mScreenFirstRow % mTotalRows);
mTotalRows = newTotalRows;
mActiveTranscriptRows = altScreen ? 0 : Math.max(0, mActiveTranscriptRows + shiftDownOfTopRow);
cursor[1] -= shiftDownOfTopRow;
mScreenRows = newRows;
} else {
// Copy away old state and update new:
TerminalRow[] oldLines = mLines;
mLines = new TerminalRow[newTotalRows];
for (int i = 0; i < newTotalRows; i++)
mLines[i] = new TerminalRow(newColumns, currentStyle);
final int oldActiveTranscriptRows = mActiveTranscriptRows;
final int oldScreenFirstRow = mScreenFirstRow;
final int oldScreenRows = mScreenRows;
final int oldTotalRows = mTotalRows;
mTotalRows = newTotalRows;
mScreenRows = newRows;
mActiveTranscriptRows = mScreenFirstRow = 0;
mColumns = newColumns;
int newCursorRow = -1;
int newCursorColumn = -1;
int oldCursorRow = cursor[1];
int oldCursorColumn = cursor[0];
boolean newCursorPlaced = false;
int currentOutputExternalRow = 0;
int currentOutputExternalColumn = 0;
// Loop over every character in the initial state.
// Blank lines should be skipped only if at end of transcript (just as is done in the "fast" resize), so we
// keep track how many blank lines we have skipped if we later on find a non-blank line.
int skippedBlankLines = 0;
for (int externalOldRow = -oldActiveTranscriptRows; externalOldRow < oldScreenRows; externalOldRow++) {
// Do what externalToInternalRow() does but for the old state:
int internalOldRow = oldScreenFirstRow + externalOldRow;
internalOldRow = (internalOldRow < 0) ? (oldTotalRows + internalOldRow) : (internalOldRow % oldTotalRows);
TerminalRow oldLine = oldLines[internalOldRow];
boolean cursorAtThisRow = externalOldRow == oldCursorRow;
// The cursorColor may only be on a non-null line, which we should not skip:
if (oldLine == null || (!(!newCursorPlaced && cursorAtThisRow)) && oldLine.isBlank()) {
skippedBlankLines++;
continue;
} else if (skippedBlankLines > 0) {
// After skipping some blank lines we encounter a non-blank line. Insert the skipped blank lines.
for (int i = 0; i < skippedBlankLines; i++) {
if (currentOutputExternalRow == mScreenRows - 1) {
scrollDownOneLine(0, mScreenRows, currentStyle);
} else { } else {
x2 = columns; currentOutputExternalRow++;
} }
TerminalRow lineObject = mLines[externalToInternalRow(row)]; currentOutputExternalColumn = 0;
int x1Index = lineObject.findStartOfColumn(x1); }
int x2Index = (x2 < mColumns) ? lineObject.findStartOfColumn(x2) : lineObject.getSpaceUsed(); skippedBlankLines = 0;
if (x2Index == x1Index) { }
// Selected the start of a wide character.
x2Index = lineObject.findStartOfColumn(x2 + 1); int lastNonSpaceIndex = 0;
} boolean justToCursor = false;
char[] line = lineObject.mText; if (cursorAtThisRow || oldLine.mLineWrap) {
int lastPrintingCharIndex = -1; // Take the whole line, either because of cursorColor on it, or if line wrapping.
int i; lastNonSpaceIndex = oldLine.getSpaceUsed();
boolean rowLineWrap = getLineWrap(row); if (cursorAtThisRow) justToCursor = true;
if (rowLineWrap && x2 == columns) { } else {
// If the line was wrapped, we shouldn't lose trailing space: for (int i = 0; i < oldLine.getSpaceUsed(); i++)
lastPrintingCharIndex = x2Index - 1; // NEWLY INTRODUCED BUG! Should not index oldLine.mStyle with char indices
if (oldLine.mText[i] != ' '/* || oldLine.mStyle[i] != currentStyle */)
lastNonSpaceIndex = i + 1;
}
int currentOldCol = 0;
long styleAtCol = 0;
for (int i = 0; i < lastNonSpaceIndex; i++) {
// Note that looping over java character, not cells.
char c = oldLine.mText[i];
int codePoint = (Character.isHighSurrogate(c)) ? Character.toCodePoint(c, oldLine.mText[++i]) : c;
int displayWidth = WcWidth.width(codePoint);
// Use the last style if this is a zero-width character:
if (displayWidth > 0) styleAtCol = oldLine.getStyle(currentOldCol);
// Line wrap as necessary:
if (currentOutputExternalColumn + displayWidth > mColumns) {
setLineWrap(currentOutputExternalRow);
if (currentOutputExternalRow == mScreenRows - 1) {
if (newCursorPlaced) newCursorRow--;
scrollDownOneLine(0, mScreenRows, currentStyle);
} else { } else {
for (i = x1Index; i < x2Index; ++i) { currentOutputExternalRow++;
char c = line[i];
if (c != ' ') lastPrintingCharIndex = i;
}
} }
if (lastPrintingCharIndex != -1) currentOutputExternalColumn = 0;
builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1); }
if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n');
int offsetDueToCombiningChar = ((displayWidth <= 0 && currentOutputExternalColumn > 0) ? 1 : 0);
int outputColumn = currentOutputExternalColumn - offsetDueToCombiningChar;
setChar(outputColumn, currentOutputExternalRow, codePoint, styleAtCol);
if (displayWidth > 0) {
if (oldCursorRow == externalOldRow && oldCursorColumn == currentOldCol) {
newCursorColumn = currentOutputExternalColumn;
newCursorRow = currentOutputExternalRow;
newCursorPlaced = true;
}
currentOldCol += displayWidth;
currentOutputExternalColumn += displayWidth;
if (justToCursor && newCursorPlaced) break;
}
} }
return builder.toString(); // Old row has been copied. Check if we need to insert newline if old line was not wrapping:
if (externalOldRow != (oldScreenRows - 1) && !oldLine.mLineWrap) {
if (currentOutputExternalRow == mScreenRows - 1) {
if (newCursorPlaced) newCursorRow--;
scrollDownOneLine(0, mScreenRows, currentStyle);
} else {
currentOutputExternalRow++;
}
currentOutputExternalColumn = 0;
}
}
cursor[0] = newCursorColumn;
cursor[1] = newCursorRow;
} }
public int getActiveTranscriptRows() { // Handle cursorColor scrolling off screen:
return mActiveTranscriptRows; if (cursor[0] < 0 || cursor[1] < 0) cursor[0] = cursor[1] = 0;
} }
public int getActiveRows() { /**
return mActiveTranscriptRows + mScreenRows; * Block copy lines and associated metadata from one location to another in the circular buffer, taking wraparound
} * into account.
*
* @param srcInternal The first line to be copied.
* @param len The number of lines to be copied.
*/
private void blockCopyLinesDown(int srcInternal, int len) {
if (len == 0) return;
int totalRows = mTotalRows;
/** int start = len - 1;
* Convert a row value from the public external coordinate system to our internal private coordinate system. // Save away line to be overwritten:
* TerminalRow lineToBeOverWritten = mLines[(srcInternal + start + 1) % totalRows];
* <pre> // Do the copy from bottom to top.
* - External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1. for (int i = start; i >= 0; --i)
* - Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the mLines[(srcInternal + i + 1) % totalRows] = mLines[(srcInternal + i) % totalRows];
* mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer). // Put back overwritten line, now above the block:
* mLines[(srcInternal) % totalRows] = lineToBeOverWritten;
* External Internal: }
*
* [ ... ] [ ... ]
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
* [ ... ] [ ... ]
* [ 0 (visible screen starts here) ] [ mScreenFirstRow ]
* [ ... ] [ ... ]
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
* </pre>
*
* @param externalRow a row in the external coordinate system.
* @return The row corresponding to the input argument in the private coordinate system.
*/
public int externalToInternalRow(int externalRow) {
if (externalRow < -mActiveTranscriptRows || externalRow > mScreenRows)
throw new IllegalArgumentException("extRow=" + externalRow + ", mScreenRows=" + mScreenRows + ", mActiveTranscriptRows=" + mActiveTranscriptRows);
final int internalRow = mScreenFirstRow + externalRow;
return (internalRow < 0) ? (mTotalRows + internalRow) : (internalRow % mTotalRows);
}
public void setLineWrap(int row) { /**
mLines[externalToInternalRow(row)].mLineWrap = true; * Scroll the screen down one line. To scroll the whole screen of a 24 line screen, the arguments would be (0, 24).
} *
* @param topMargin First line that is scrolled.
* @param bottomMargin One line after the last line that is scrolled.
* @param style the style for the newly exposed line.
*/
public void scrollDownOneLine(int topMargin, int bottomMargin, long style) {
if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows)
throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + mScreenRows);
public boolean getLineWrap(int row) { // Copy the fixed topMargin lines one line down so that they remain on screen in same position:
return mLines[externalToInternalRow(row)].mLineWrap; blockCopyLinesDown(mScreenFirstRow, topMargin);
} // Copy the fixed mScreenRows-bottomMargin lines one line down so that they remain on screen in same
// position:
blockCopyLinesDown(externalToInternalRow(bottomMargin), mScreenRows - bottomMargin);
public void clearLineWrap(int row) { // Update the screen location in the ring buffer:
mLines[externalToInternalRow(row)].mLineWrap = false; mScreenFirstRow = (mScreenFirstRow + 1) % mTotalRows;
} // Note that the history has grown if not already full:
if (mActiveTranscriptRows < mTotalRows - mScreenRows) mActiveTranscriptRows++;
/** // Blank the newly revealed line above the bottom margin:
* Resize the screen which this transcript backs. Currently, this only works if the number of columns does not int blankRow = externalToInternalRow(bottomMargin - 1);
* change or the rows expand (that is, it only works when shrinking the number of rows). if (mLines[blankRow] == null) {
* mLines[blankRow] = new TerminalRow(mColumns, style);
* @param newColumns The number of columns the screen should have. } else {
* @param newRows The number of rows the screen should have. mLines[blankRow].clear(style);
* @param cursor An int[2] containing the (column, row) cursorColor location. }
*/ }
public void resize(int newColumns, int newRows, int newTotalRows, int[] cursor, long currentStyle, boolean altScreen) {
// newRows > mTotalRows should not normally happen since mTotalRows is TRANSCRIPT_ROWS (10000): /**
if (newColumns == mColumns && newRows <= mTotalRows) { * Block copy characters from one position in the screen to another. The two positions can overlap. All characters
// Fast resize where just the rows changed. * of the source and destination must be within the bounds of the screen, or else an InvalidParameterException will
int shiftDownOfTopRow = mScreenRows - newRows; * be thrown.
if (shiftDownOfTopRow > 0 && shiftDownOfTopRow < mScreenRows) { *
// Shrinking. Check if we can skip blank rows at bottom below cursorColor. * @param sx source X coordinate
for (int i = mScreenRows - 1; i > 0; i--) { * @param sy source Y coordinate
if (cursor[1] >= i) break; * @param w width
int r = externalToInternalRow(i); * @param h height
if (mLines[r] == null || mLines[r].isBlank()) { * @param dx destination X coordinate
if (--shiftDownOfTopRow == 0) break; * @param dy destination Y coordinate
} */
} public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
} else if (shiftDownOfTopRow < 0) { if (w == 0) return;
// Negative shift down = expanding. Only move screen up if there is transcript to show: if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows || dx < 0 || dx + w > mColumns || dy < 0 || dy + h > mScreenRows)
int actualShift = Math.max(shiftDownOfTopRow, -mActiveTranscriptRows); throw new IllegalArgumentException();
if (shiftDownOfTopRow != actualShift) { boolean copyingUp = sy > dy;
// The new lines revealed by the resizing are not all from the transcript. Blank the below ones. for (int y = 0; y < h; y++) {
for (int i = 0; i < actualShift - shiftDownOfTopRow; i++) int y2 = copyingUp ? y : (h - (y + 1));
allocateFullLineIfNecessary((mScreenFirstRow + mScreenRows + i) % mTotalRows).clear(currentStyle); TerminalRow sourceRow = allocateFullLineIfNecessary(externalToInternalRow(sy + y2));
shiftDownOfTopRow = actualShift; allocateFullLineIfNecessary(externalToInternalRow(dy + y2)).copyInterval(sourceRow, sx, sx + w, dx);
} }
} }
mScreenFirstRow += shiftDownOfTopRow;
mScreenFirstRow = (mScreenFirstRow < 0) ? (mScreenFirstRow + mTotalRows) : (mScreenFirstRow % mTotalRows); /**
mTotalRows = newTotalRows; * Block set characters. All characters must be within the bounds of the screen, or else and
mActiveTranscriptRows = altScreen ? 0 : Math.max(0, mActiveTranscriptRows + shiftDownOfTopRow); * InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block
cursor[1] -= shiftDownOfTopRow; * of characters.
mScreenRows = newRows; */
public void blockSet(int sx, int sy, int w, int h, int val, long style) {
if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
throw new IllegalArgumentException(
"Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")");
}
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++)
setChar(sx + x, sy + y, val, style);
}
public TerminalRow allocateFullLineIfNecessary(int row) {
return (mLines[row] == null) ? (mLines[row] = new TerminalRow(mColumns, 0)) : mLines[row];
}
public void setChar(int column, int row, int codePoint, long style) {
if (row >= mScreenRows || column >= mColumns)
throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
row = externalToInternalRow(row);
allocateFullLineIfNecessary(row).setChar(column, codePoint, style);
}
public long getStyleAt(int externalRow, int column) {
return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column);
}
/**
* Support for http://vt100.net/docs/vt510-rm/DECCARA and http://vt100.net/docs/vt510-rm/DECCARA
*/
public void setOrClearEffect(int bits, boolean setOrClear, boolean reverse, boolean rectangular, int leftMargin, int rightMargin, int top, int left,
int bottom, int right) {
for (int y = top; y < bottom; y++) {
TerminalRow line = mLines[externalToInternalRow(y)];
int startOfLine = (rectangular || y == top) ? left : leftMargin;
int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin;
for (int x = startOfLine; x < endOfLine; x++) {
long currentStyle = line.getStyle(x);
int foreColor = TextStyle.decodeForeColor(currentStyle);
int backColor = TextStyle.decodeBackColor(currentStyle);
int effect = TextStyle.decodeEffect(currentStyle);
if (reverse) {
// Clear out the bits to reverse and add them back in reversed:
effect = (effect & ~bits) | (bits & ~effect);
} else if (setOrClear) {
effect |= bits;
} else { } else {
// Copy away old state and update new: effect &= ~bits;
TerminalRow[] oldLines = mLines;
mLines = new TerminalRow[newTotalRows];
for (int i = 0; i < newTotalRows; i++)
mLines[i] = new TerminalRow(newColumns, currentStyle);
final int oldActiveTranscriptRows = mActiveTranscriptRows;
final int oldScreenFirstRow = mScreenFirstRow;
final int oldScreenRows = mScreenRows;
final int oldTotalRows = mTotalRows;
mTotalRows = newTotalRows;
mScreenRows = newRows;
mActiveTranscriptRows = mScreenFirstRow = 0;
mColumns = newColumns;
int newCursorRow = -1;
int newCursorColumn = -1;
int oldCursorRow = cursor[1];
int oldCursorColumn = cursor[0];
boolean newCursorPlaced = false;
int currentOutputExternalRow = 0;
int currentOutputExternalColumn = 0;
// Loop over every character in the initial state.
// Blank lines should be skipped only if at end of transcript (just as is done in the "fast" resize), so we
// keep track how many blank lines we have skipped if we later on find a non-blank line.
int skippedBlankLines = 0;
for (int externalOldRow = -oldActiveTranscriptRows; externalOldRow < oldScreenRows; externalOldRow++) {
// Do what externalToInternalRow() does but for the old state:
int internalOldRow = oldScreenFirstRow + externalOldRow;
internalOldRow = (internalOldRow < 0) ? (oldTotalRows + internalOldRow) : (internalOldRow % oldTotalRows);
TerminalRow oldLine = oldLines[internalOldRow];
boolean cursorAtThisRow = externalOldRow == oldCursorRow;
// The cursorColor may only be on a non-null line, which we should not skip:
if (oldLine == null || (!(!newCursorPlaced && cursorAtThisRow)) && oldLine.isBlank()) {
skippedBlankLines++;
continue;
} else if (skippedBlankLines > 0) {
// After skipping some blank lines we encounter a non-blank line. Insert the skipped blank lines.
for (int i = 0; i < skippedBlankLines; i++) {
if (currentOutputExternalRow == mScreenRows - 1) {
scrollDownOneLine(0, mScreenRows, currentStyle);
} else {
currentOutputExternalRow++;
}
currentOutputExternalColumn = 0;
}
skippedBlankLines = 0;
}
int lastNonSpaceIndex = 0;
boolean justToCursor = false;
if (cursorAtThisRow || oldLine.mLineWrap) {
// Take the whole line, either because of cursorColor on it, or if line wrapping.
lastNonSpaceIndex = oldLine.getSpaceUsed();
if (cursorAtThisRow) justToCursor = true;
} else {
for (int i = 0; i < oldLine.getSpaceUsed(); i++)
// NEWLY INTRODUCED BUG! Should not index oldLine.mStyle with char indices
if (oldLine.mText[i] != ' '/* || oldLine.mStyle[i] != currentStyle */)
lastNonSpaceIndex = i + 1;
}
int currentOldCol = 0;
long styleAtCol = 0;
for (int i = 0; i < lastNonSpaceIndex; i++) {
// Note that looping over java character, not cells.
char c = oldLine.mText[i];
int codePoint = (Character.isHighSurrogate(c)) ? Character.toCodePoint(c, oldLine.mText[++i]) : c;
int displayWidth = WcWidth.width(codePoint);
// Use the last style if this is a zero-width character:
if (displayWidth > 0) styleAtCol = oldLine.getStyle(currentOldCol);
// Line wrap as necessary:
if (currentOutputExternalColumn + displayWidth > mColumns) {
setLineWrap(currentOutputExternalRow);
if (currentOutputExternalRow == mScreenRows - 1) {
if (newCursorPlaced) newCursorRow--;
scrollDownOneLine(0, mScreenRows, currentStyle);
} else {
currentOutputExternalRow++;
}
currentOutputExternalColumn = 0;
}
int offsetDueToCombiningChar = ((displayWidth <= 0 && currentOutputExternalColumn > 0) ? 1 : 0);
int outputColumn = currentOutputExternalColumn - offsetDueToCombiningChar;
setChar(outputColumn, currentOutputExternalRow, codePoint, styleAtCol);
if (displayWidth > 0) {
if (oldCursorRow == externalOldRow && oldCursorColumn == currentOldCol) {
newCursorColumn = currentOutputExternalColumn;
newCursorRow = currentOutputExternalRow;
newCursorPlaced = true;
}
currentOldCol += displayWidth;
currentOutputExternalColumn += displayWidth;
if (justToCursor && newCursorPlaced) break;
}
}
// Old row has been copied. Check if we need to insert newline if old line was not wrapping:
if (externalOldRow != (oldScreenRows - 1) && !oldLine.mLineWrap) {
if (currentOutputExternalRow == mScreenRows - 1) {
if (newCursorPlaced) newCursorRow--;
scrollDownOneLine(0, mScreenRows, currentStyle);
} else {
currentOutputExternalRow++;
}
currentOutputExternalColumn = 0;
}
}
cursor[0] = newCursorColumn;
cursor[1] = newCursorRow;
}
// Handle cursorColor scrolling off screen:
if (cursor[0] < 0 || cursor[1] < 0) cursor[0] = cursor[1] = 0;
}
/**
* Block copy lines and associated metadata from one location to another in the circular buffer, taking wraparound
* into account.
*
* @param srcInternal The first line to be copied.
* @param len The number of lines to be copied.
*/
private void blockCopyLinesDown(int srcInternal, int len) {
if (len == 0) return;
int totalRows = mTotalRows;
int start = len - 1;
// Save away line to be overwritten:
TerminalRow lineToBeOverWritten = mLines[(srcInternal + start + 1) % totalRows];
// Do the copy from bottom to top.
for (int i = start; i >= 0; --i)
mLines[(srcInternal + i + 1) % totalRows] = mLines[(srcInternal + i) % totalRows];
// Put back overwritten line, now above the block:
mLines[(srcInternal) % totalRows] = lineToBeOverWritten;
}
/**
* Scroll the screen down one line. To scroll the whole screen of a 24 line screen, the arguments would be (0, 24).
*
* @param topMargin First line that is scrolled.
* @param bottomMargin One line after the last line that is scrolled.
* @param style the style for the newly exposed line.
*/
public void scrollDownOneLine(int topMargin, int bottomMargin, long style) {
if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows)
throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + mScreenRows);
// Copy the fixed topMargin lines one line down so that they remain on screen in same position:
blockCopyLinesDown(mScreenFirstRow, topMargin);
// Copy the fixed mScreenRows-bottomMargin lines one line down so that they remain on screen in same
// position:
blockCopyLinesDown(externalToInternalRow(bottomMargin), mScreenRows - bottomMargin);
// Update the screen location in the ring buffer:
mScreenFirstRow = (mScreenFirstRow + 1) % mTotalRows;
// Note that the history has grown if not already full:
if (mActiveTranscriptRows < mTotalRows - mScreenRows) mActiveTranscriptRows++;
// Blank the newly revealed line above the bottom margin:
int blankRow = externalToInternalRow(bottomMargin - 1);
if (mLines[blankRow] == null) {
mLines[blankRow] = new TerminalRow(mColumns, style);
} else {
mLines[blankRow].clear(style);
}
}
/**
* Block copy characters from one position in the screen to another. The two positions can overlap. All characters
* of the source and destination must be within the bounds of the screen, or else an InvalidParameterException will
* be thrown.
*
* @param sx source X coordinate
* @param sy source Y coordinate
* @param w width
* @param h height
* @param dx destination X coordinate
* @param dy destination Y coordinate
*/
public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
if (w == 0) return;
if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows || dx < 0 || dx + w > mColumns || dy < 0 || dy + h > mScreenRows)
throw new IllegalArgumentException();
boolean copyingUp = sy > dy;
for (int y = 0; y < h; y++) {
int y2 = copyingUp ? y : (h - (y + 1));
TerminalRow sourceRow = allocateFullLineIfNecessary(externalToInternalRow(sy + y2));
allocateFullLineIfNecessary(externalToInternalRow(dy + y2)).copyInterval(sourceRow, sx, sx + w, dx);
}
}
/**
* Block set characters. All characters must be within the bounds of the screen, or else and
* InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block
* of characters.
*/
public void blockSet(int sx, int sy, int w, int h, int val, long style) {
if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
throw new IllegalArgumentException(
"Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")");
}
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++)
setChar(sx + x, sy + y, val, style);
}
public TerminalRow allocateFullLineIfNecessary(int row) {
return (mLines[row] == null) ? (mLines[row] = new TerminalRow(mColumns, 0)) : mLines[row];
}
public void setChar(int column, int row, int codePoint, long style) {
if (row >= mScreenRows || column >= mColumns)
throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
row = externalToInternalRow(row);
allocateFullLineIfNecessary(row).setChar(column, codePoint, style);
}
public long getStyleAt(int externalRow, int column) {
return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column);
}
/** Support for http://vt100.net/docs/vt510-rm/DECCARA and http://vt100.net/docs/vt510-rm/DECCARA */
public void setOrClearEffect(int bits, boolean setOrClear, boolean reverse, boolean rectangular, int leftMargin, int rightMargin, int top, int left,
int bottom, int right) {
for (int y = top; y < bottom; y++) {
TerminalRow line = mLines[externalToInternalRow(y)];
int startOfLine = (rectangular || y == top) ? left : leftMargin;
int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin;
for (int x = startOfLine; x < endOfLine; x++) {
long currentStyle = line.getStyle(x);
int foreColor = TextStyle.decodeForeColor(currentStyle);
int backColor = TextStyle.decodeBackColor(currentStyle);
int effect = TextStyle.decodeEffect(currentStyle);
if (reverse) {
// Clear out the bits to reverse and add them back in reversed:
effect = (effect & ~bits) | (bits & ~effect);
} else if (setOrClear) {
effect |= bits;
} else {
effect &= ~bits;
}
line.mStyle[x] = TextStyle.encode(foreColor, backColor, effect);
}
} }
line.mStyle[x] = TextStyle.encode(foreColor, backColor, effect);
}
} }
}
} }

View File

@ -11,110 +11,112 @@ import java.util.Properties;
*/ */
public final class TerminalColorScheme { public final class TerminalColorScheme {
/** http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg, but with blue color brighter. */ /**
public static final int[] DEFAULT_COLORS = { * http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg, but with blue color brighter.
// 16 original colors. First 8 are dim. */
0xff000000, // black public static final int[] DEFAULT_COLORS = {
0xffcd0000, // dim red // 16 original colors. First 8 are dim.
0xff00cd00, // dim green 0xff000000, // black
0xffcdcd00, // dim yellow 0xffcd0000, // dim red
0xff6495ed, // dim blue 0xff00cd00, // dim green
0xffcd00cd, // dim magenta 0xffcdcd00, // dim yellow
0xff00cdcd, // dim cyan 0xff6495ed, // dim blue
0xffe5e5e5, // dim white 0xffcd00cd, // dim magenta
// Second 8 are bright: 0xff00cdcd, // dim cyan
0xff7f7f7f, // medium grey 0xffe5e5e5, // dim white
0xffff0000, // bright red // Second 8 are bright:
0xff00ff00, // bright green 0xff7f7f7f, // medium grey
0xffffff00, // bright yellow 0xffff0000, // bright red
0xff5c5cff, // light blue 0xff00ff00, // bright green
0xffff00ff, // bright magenta 0xffffff00, // bright yellow
0xff00ffff, // bright cyan 0xff5c5cff, // light blue
0xffffffff, // bright white 0xffff00ff, // bright magenta
0xff00ffff, // bright cyan
0xffffffff, // bright white
// 216 color cube, six shades of each color: // 216 color cube, six shades of each color:
0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, 0xff005fd7, 0xff005fff, 0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, 0xff005fd7, 0xff005fff,
0xff008700, 0xff00875f, 0xff008787, 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff, 0xff008700, 0xff00875f, 0xff008787, 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff,
0xff00d700, 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, 0xff00ffff, 0xff00d700, 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, 0xff00ffff,
0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff, 0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff,
0xff5f8700, 0xff5f875f, 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff, 0xff5f8700, 0xff5f875f, 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff,
0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, 0xff5fffd7, 0xff5fffff, 0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, 0xff5fffd7, 0xff5fffff,
0xff870000, 0xff87005f, 0xff870087, 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff, 0xff870000, 0xff87005f, 0xff870087, 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff,
0xff878700, 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, 0xff87afff, 0xff878700, 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, 0xff87afff,
0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, 0xff87ffaf, 0xff87ffd7, 0xff87ffff, 0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, 0xff87ffaf, 0xff87ffd7, 0xff87ffff,
0xffaf0000, 0xffaf005f, 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff, 0xffaf0000, 0xffaf005f, 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff,
0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, 0xffafafd7, 0xffafafff, 0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, 0xffafafd7, 0xffafafff,
0xffafd700, 0xffafd75f, 0xffafd787, 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff, 0xffafd700, 0xffafd75f, 0xffafd787, 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff,
0xffd70000, 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, 0xffd75fff, 0xffd70000, 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, 0xffd75fff,
0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, 0xffd7afaf, 0xffd7afd7, 0xffd7afff, 0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, 0xffd7afaf, 0xffd7afd7, 0xffd7afff,
0xffd7d700, 0xffd7d75f, 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff, 0xffd7d700, 0xffd7d75f, 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff,
0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, 0xffff5fd7, 0xffff5fff, 0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, 0xffff5fd7, 0xffff5fff,
0xffff8700, 0xffff875f, 0xffff8787, 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff, 0xffff8700, 0xffff875f, 0xffff8787, 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff,
0xffffd700, 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, 0xffffffff, 0xffffd700, 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, 0xffffffff,
// 24 grey scale ramp: // 24 grey scale ramp:
0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff626262, 0xff6c6c6c, 0xff767676, 0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff626262, 0xff6c6c6c, 0xff767676,
0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee, 0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee,
// COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR: // COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR:
0xffffffff, 0xff000000, 0xffA9AAA9}; 0xffffffff, 0xff000000, 0xffA9AAA9};
public final int[] mDefaultColors = new int[TextStyle.NUM_INDEXED_COLORS]; public final int[] mDefaultColors = new int[TextStyle.NUM_INDEXED_COLORS];
public TerminalColorScheme() { public TerminalColorScheme() {
reset(); reset();
}
private void reset() {
System.arraycopy(DEFAULT_COLORS, 0, mDefaultColors, 0, TextStyle.NUM_INDEXED_COLORS);
}
public void updateWith(String foreground, String background, String cursor, Map<Integer, String> color) {
Properties prop = new Properties();
if (foreground != null) {
prop.put("foreground", foreground);
} }
if (background != null) {
private void reset() { prop.put("background", background);
System.arraycopy(DEFAULT_COLORS, 0, mDefaultColors, 0, TextStyle.NUM_INDEXED_COLORS);
} }
if (cursor != null) {
public void updateWith(String foreground, String background, String cursor, Map<Integer, String> color) { prop.put("cursor", cursor);
Properties prop = new Properties();
if (foreground != null) {
prop.put("foreground", foreground);
}
if (background != null) {
prop.put("background", background);
}
if (cursor != null) {
prop.put("cursor", cursor);
}
for (int i : color.keySet()) {
prop.put("color" + i, color.get(i));
}
updateWith(prop);
} }
for (int i : color.keySet()) {
public void updateWith(Properties props) { prop.put("color" + i, color.get(i));
reset();
for (Map.Entry<Object, Object> entries : props.entrySet()) {
String key = (String) entries.getKey();
String value = (String) entries.getValue();
int colorIndex;
if (key.equals("foreground")) {
colorIndex = TextStyle.COLOR_INDEX_FOREGROUND;
} else if (key.equals("background")) {
colorIndex = TextStyle.COLOR_INDEX_BACKGROUND;
} else if (key.equals("cursor")) {
colorIndex = TextStyle.COLOR_INDEX_CURSOR;
} else if (key.startsWith("color")) {
try {
colorIndex = Integer.parseInt(key.substring(5));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid property: '" + key + "'");
}
} else {
throw new IllegalArgumentException("Invalid property: '" + key + "'");
}
int colorValue = TerminalColors.parse(value);
if (colorValue == 0)
throw new IllegalArgumentException("Property '" + key + "' has invalid color: '" + value + "'");
mDefaultColors[colorIndex] = colorValue;
}
} }
updateWith(prop);
}
public void updateWith(Properties props) {
reset();
for (Map.Entry<Object, Object> entries : props.entrySet()) {
String key = (String) entries.getKey();
String value = (String) entries.getValue();
int colorIndex;
if (key.equals("foreground")) {
colorIndex = TextStyle.COLOR_INDEX_FOREGROUND;
} else if (key.equals("background")) {
colorIndex = TextStyle.COLOR_INDEX_BACKGROUND;
} else if (key.equals("cursor")) {
colorIndex = TextStyle.COLOR_INDEX_CURSOR;
} else if (key.startsWith("color")) {
try {
colorIndex = Integer.parseInt(key.substring(5));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid property: '" + key + "'");
}
} else {
throw new IllegalArgumentException("Invalid property: '" + key + "'");
}
int colorValue = TerminalColors.parse(value);
if (colorValue == 0)
throw new IllegalArgumentException("Property '" + key + "' has invalid color: '" + value + "'");
mDefaultColors[colorIndex] = colorValue;
}
}
} }

View File

@ -1,81 +1,93 @@
package io.neoterm.backend; package io.neoterm.backend;
/** Current terminal colors (if different from default). */ /**
* Current terminal colors (if different from default).
*/
public final class TerminalColors { public final class TerminalColors {
/** Static data - a bit ugly but ok for now. */ /**
public static final TerminalColorScheme COLOR_SCHEME = new TerminalColorScheme(); * Static data - a bit ugly but ok for now.
*/
public static final TerminalColorScheme COLOR_SCHEME = new TerminalColorScheme();
/** /**
* The current terminal colors, which are normally set from the color theme, but may be set dynamically with the OSC * The current terminal colors, which are normally set from the color theme, but may be set dynamically with the OSC
* 4 control sequence. * 4 control sequence.
*/ */
public final int[] mCurrentColors = new int[TextStyle.NUM_INDEXED_COLORS]; public final int[] mCurrentColors = new int[TextStyle.NUM_INDEXED_COLORS];
/** Create a new instance with default colors from the theme. */ /**
public TerminalColors() { * Create a new instance with default colors from the theme.
reset(); */
public TerminalColors() {
reset();
}
/**
* Reset a particular indexed color with the default color from the color theme.
*/
public void reset(int index) {
mCurrentColors[index] = COLOR_SCHEME.mDefaultColors[index];
}
/**
* Reset all indexed colors with the default color from the color theme.
*/
public void reset() {
reset(COLOR_SCHEME);
}
public void reset(TerminalColorScheme colorScheme) {
System.arraycopy(colorScheme.mDefaultColors, 0, mCurrentColors, 0, TextStyle.NUM_INDEXED_COLORS);
}
/**
* Parse color according to http://manpages.ubuntu.com/manpages/intrepid/man3/XQueryColor.3.html
* <p/>
* Highest bit is set if successful, so return value is 0xFF${R}${G}${B}. Return 0 if failed.
*/
public static int parse(String c) {
try {
int skipInitial, skipBetween;
if (c.charAt(0) == '#') {
// #RGB, #RRGGBB, #RRRGGGBBB or #RRRRGGGGBBBB. Most significant bits.
skipInitial = 1;
skipBetween = 0;
} else if (c.startsWith("rgb:")) {
// rgb:<red>/<green>/<blue> where <red>, <green>, <blue> := h | hh | hhh | hhhh. Scaled.
skipInitial = 4;
skipBetween = 1;
} else {
// assume that c is an int
return Integer.parseInt(c);
}
int charsForColors = c.length() - skipInitial - 2 * skipBetween;
if (charsForColors % 3 != 0) return 0; // Unequal lengths.
int componentLength = charsForColors / 3;
double mult = 255 / (Math.pow(2, componentLength * 4) - 1);
int currentPosition = skipInitial;
String rString = c.substring(currentPosition, currentPosition + componentLength);
currentPosition += componentLength + skipBetween;
String gString = c.substring(currentPosition, currentPosition + componentLength);
currentPosition += componentLength + skipBetween;
String bString = c.substring(currentPosition, currentPosition + componentLength);
int r = (int) (Integer.parseInt(rString, 16) * mult);
int g = (int) (Integer.parseInt(gString, 16) * mult);
int b = (int) (Integer.parseInt(bString, 16) * mult);
return 0xFF << 24 | r << 16 | g << 8 | b;
} catch (NumberFormatException | IndexOutOfBoundsException e) {
return 0;
} }
}
/** Reset a particular indexed color with the default color from the color theme. */ /**
public void reset(int index) { * Try parse a color from a text parameter and into a specified index.
mCurrentColors[index] = COLOR_SCHEME.mDefaultColors[index]; */
} public void tryParseColor(int intoIndex, String textParameter) {
int c = parse(textParameter);
/** Reset all indexed colors with the default color from the color theme. */ if (c != 0) mCurrentColors[intoIndex] = c;
public void reset() { }
reset(COLOR_SCHEME);
}
public void reset(TerminalColorScheme colorScheme) {
System.arraycopy(colorScheme.mDefaultColors, 0, mCurrentColors, 0, TextStyle.NUM_INDEXED_COLORS);
}
/**
* Parse color according to http://manpages.ubuntu.com/manpages/intrepid/man3/XQueryColor.3.html
* <p/>
* Highest bit is set if successful, so return value is 0xFF${R}${G}${B}. Return 0 if failed.
*/
public static int parse(String c) {
try {
int skipInitial, skipBetween;
if (c.charAt(0) == '#') {
// #RGB, #RRGGBB, #RRRGGGBBB or #RRRRGGGGBBBB. Most significant bits.
skipInitial = 1;
skipBetween = 0;
} else if (c.startsWith("rgb:")) {
// rgb:<red>/<green>/<blue> where <red>, <green>, <blue> := h | hh | hhh | hhhh. Scaled.
skipInitial = 4;
skipBetween = 1;
} else {
// assume that c is an int
return Integer.parseInt(c);
}
int charsForColors = c.length() - skipInitial - 2 * skipBetween;
if (charsForColors % 3 != 0) return 0; // Unequal lengths.
int componentLength = charsForColors / 3;
double mult = 255 / (Math.pow(2, componentLength * 4) - 1);
int currentPosition = skipInitial;
String rString = c.substring(currentPosition, currentPosition + componentLength);
currentPosition += componentLength + skipBetween;
String gString = c.substring(currentPosition, currentPosition + componentLength);
currentPosition += componentLength + skipBetween;
String bString = c.substring(currentPosition, currentPosition + componentLength);
int r = (int) (Integer.parseInt(rString, 16) * mult);
int g = (int) (Integer.parseInt(gString, 16) * mult);
int b = (int) (Integer.parseInt(bString, 16) * mult);
return 0xFF << 24 | r << 16 | g << 8 | b;
} catch (NumberFormatException | IndexOutOfBoundsException e) {
return 0;
}
}
/** Try parse a color from a text parameter and into a specified index. */
public void tryParseColor(int intoIndex, String textParameter) {
int c = parse(textParameter);
if (c != 0) mCurrentColors[intoIndex] = c;
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -2,27 +2,39 @@ package io.neoterm.backend;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
/** A client which receives callbacks from events triggered by feeding input to a {@link TerminalEmulator}. */ /**
* A client which receives callbacks from events triggered by feeding input to a {@link TerminalEmulator}.
*/
public abstract class TerminalOutput { public abstract class TerminalOutput {
/** Write a string using the UTF-8 encoding to the terminal client. */ /**
public final void write(String data) { * Write a string using the UTF-8 encoding to the terminal client.
byte[] bytes = data.getBytes(StandardCharsets.UTF_8); */
write(bytes, 0, bytes.length); public final void write(String data) {
} byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
write(bytes, 0, bytes.length);
}
/** Write bytes to the terminal client. */ /**
public abstract void write(byte[] data, int offset, int count); * Write bytes to the terminal client.
*/
public abstract void write(byte[] data, int offset, int count);
/** Notify the terminal client that the terminal title has changed. */ /**
public abstract void titleChanged(String oldTitle, String newTitle); * Notify the terminal client that the terminal title has changed.
*/
public abstract void titleChanged(String oldTitle, String newTitle);
/** Notify the terminal client that the terminal title has changed. */ /**
public abstract void clipboardText(String text); * Notify the terminal client that the terminal title has changed.
*/
public abstract void clipboardText(String text);
/** Notify the terminal client that a bell character (ASCII 7, bell, BEL, \a, ^G)) has been received. */ /**
public abstract void onBell(); * Notify the terminal client that a bell character (ASCII 7, bell, BEL, \a, ^G)) has been received.
*/
public abstract void onBell();
public abstract void onColorsChanged(); public abstract void onColorsChanged();
} }

View File

@ -9,239 +9,257 @@ import java.util.Arrays;
*/ */
public final class TerminalRow { public final class TerminalRow {
private static final float SPARE_CAPACITY_FACTOR = 1.5f; private static final float SPARE_CAPACITY_FACTOR = 1.5f;
/** The number of columns in this terminal row. */ /**
private final int mColumns; * The number of columns in this terminal row.
/** The text filling this terminal row. */ */
public char[] mText; private final int mColumns;
/** The number of java char:s used in {@link #mText}. */ /**
private short mSpaceUsed; * The text filling this terminal row.
/** If this row has been line wrapped due to text output at the end of line. */ */
boolean mLineWrap; public char[] mText;
/** The style bits of each cell in the row. See {@link TextStyle}. */ /**
final long[] mStyle; * The number of java char:s used in {@link #mText}.
/** If this row might contain chars with width != 1, used for deactivating fast path */ */
boolean mHasNonOneWidthOrSurrogateChars; private short mSpaceUsed;
/**
* If this row has been line wrapped due to text output at the end of line.
*/
boolean mLineWrap;
/**
* The style bits of each cell in the row. See {@link TextStyle}.
*/
final long[] mStyle;
/**
* If this row might contain chars with width != 1, used for deactivating fast path
*/
boolean mHasNonOneWidthOrSurrogateChars;
/** Construct a blank row (containing only whitespace, ' ') with a specified style. */ /**
public TerminalRow(int columns, long style) { * Construct a blank row (containing only whitespace, ' ') with a specified style.
mColumns = columns; */
mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)]; public TerminalRow(int columns, long style) {
mStyle = new long[columns]; mColumns = columns;
clear(style); mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)];
mStyle = new long[columns];
clear(style);
}
/**
* NOTE: The sourceX2 is exclusive.
*/
public void copyInterval(TerminalRow line, int sourceX1, int sourceX2, int destinationX) {
mHasNonOneWidthOrSurrogateChars |= line.mHasNonOneWidthOrSurrogateChars;
final int x1 = line.findStartOfColumn(sourceX1);
final int x2 = line.findStartOfColumn(sourceX2);
boolean startingFromSecondHalfOfWideChar = (sourceX1 > 0 && line.wideDisplayCharacterStartingAt(sourceX1 - 1));
final char[] sourceChars = (this == line) ? Arrays.copyOf(line.mText, line.mText.length) : line.mText;
int latestNonCombiningWidth = 0;
for (int i = x1; i < x2; i++) {
char sourceChar = sourceChars[i];
int codePoint = Character.isHighSurrogate(sourceChar) ? Character.toCodePoint(sourceChar, sourceChars[++i]) : sourceChar;
if (startingFromSecondHalfOfWideChar) {
// Just treat copying second half of wide char as copying whitespace.
codePoint = ' ';
startingFromSecondHalfOfWideChar = false;
}
int w = WcWidth.width(codePoint);
if (w > 0) {
destinationX += latestNonCombiningWidth;
sourceX1 += latestNonCombiningWidth;
latestNonCombiningWidth = w;
}
setChar(destinationX, codePoint, line.getStyle(sourceX1));
} }
}
/** NOTE: The sourceX2 is exclusive. */ public int getSpaceUsed() {
public void copyInterval(TerminalRow line, int sourceX1, int sourceX2, int destinationX) { return mSpaceUsed;
mHasNonOneWidthOrSurrogateChars |= line.mHasNonOneWidthOrSurrogateChars; }
final int x1 = line.findStartOfColumn(sourceX1);
final int x2 = line.findStartOfColumn(sourceX2);
boolean startingFromSecondHalfOfWideChar = (sourceX1 > 0 && line.wideDisplayCharacterStartingAt(sourceX1 - 1));
final char[] sourceChars = (this == line) ? Arrays.copyOf(line.mText, line.mText.length) : line.mText;
int latestNonCombiningWidth = 0;
for (int i = x1; i < x2; i++) {
char sourceChar = sourceChars[i];
int codePoint = Character.isHighSurrogate(sourceChar) ? Character.toCodePoint(sourceChar, sourceChars[++i]) : sourceChar;
if (startingFromSecondHalfOfWideChar) {
// Just treat copying second half of wide char as copying whitespace.
codePoint = ' ';
startingFromSecondHalfOfWideChar = false;
}
int w = WcWidth.width(codePoint);
if (w > 0) {
destinationX += latestNonCombiningWidth;
sourceX1 += latestNonCombiningWidth;
latestNonCombiningWidth = w;
}
setChar(destinationX, codePoint, line.getStyle(sourceX1));
}
}
public int getSpaceUsed() { /**
return mSpaceUsed; * Note that the column may end of second half of wide character.
} */
public int findStartOfColumn(int column) {
if (column == mColumns) return getSpaceUsed();
/** Note that the column may end of second half of wide character. */ int currentColumn = 0;
public int findStartOfColumn(int column) { int currentCharIndex = 0;
if (column == mColumns) return getSpaceUsed(); while (true) { // 0<2 1 < 2
int newCharIndex = currentCharIndex;
int currentColumn = 0; char c = mText[newCharIndex++]; // cci=1, cci=2
int currentCharIndex = 0; boolean isHigh = Character.isHighSurrogate(c);
while (true) { // 0<2 1 < 2 int codePoint = isHigh ? Character.toCodePoint(c, mText[newCharIndex++]) : c;
int newCharIndex = currentCharIndex; int wcwidth = WcWidth.width(codePoint); // 1, 2
char c = mText[newCharIndex++]; // cci=1, cci=2 if (wcwidth > 0) {
boolean isHigh = Character.isHighSurrogate(c); currentColumn += wcwidth;
int codePoint = isHigh ? Character.toCodePoint(c, mText[newCharIndex++]) : c; if (currentColumn == column) {
int wcwidth = WcWidth.width(codePoint); // 1, 2 while (newCharIndex < mSpaceUsed) {
if (wcwidth > 0) { // Skip combining chars.
currentColumn += wcwidth; if (Character.isHighSurrogate(mText[newCharIndex])) {
if (currentColumn == column) { if (WcWidth.width(Character.toCodePoint(mText[newCharIndex], mText[newCharIndex + 1])) <= 0) {
while (newCharIndex < mSpaceUsed) { newCharIndex += 2;
// Skip combining chars. } else {
if (Character.isHighSurrogate(mText[newCharIndex])) { break;
if (WcWidth.width(Character.toCodePoint(mText[newCharIndex], mText[newCharIndex + 1])) <= 0) { }
newCharIndex += 2; } else if (WcWidth.width(mText[newCharIndex]) <= 0) {
} else { newCharIndex++;
break;
}
} else if (WcWidth.width(mText[newCharIndex]) <= 0) {
newCharIndex++;
} else {
break;
}
}
return newCharIndex;
} else if (currentColumn > column) {
// Wide column going past end.
return currentCharIndex;
}
}
currentCharIndex = newCharIndex;
}
}
private boolean wideDisplayCharacterStartingAt(int column) {
for (int currentCharIndex = 0, currentColumn = 0; currentCharIndex < mSpaceUsed; ) {
char c = mText[currentCharIndex++];
int codePoint = Character.isHighSurrogate(c) ? Character.toCodePoint(c, mText[currentCharIndex++]) : c;
int wcwidth = WcWidth.width(codePoint);
if (wcwidth > 0) {
if (currentColumn == column && wcwidth == 2) return true;
currentColumn += wcwidth;
if (currentColumn > column) return false;
}
}
return false;
}
public void clear(long style) {
Arrays.fill(mText, ' ');
Arrays.fill(mStyle, style);
mSpaceUsed = (short) mColumns;
mHasNonOneWidthOrSurrogateChars = false;
}
// https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26
public void setChar(int columnToSet, int codePoint, long style) {
mStyle[columnToSet] = style;
final int newCodePointDisplayWidth = WcWidth.width(codePoint);
// Fast path when we don't have any chars with width != 1
if (!mHasNonOneWidthOrSurrogateChars) {
if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT || newCodePointDisplayWidth != 1) {
mHasNonOneWidthOrSurrogateChars = true;
} else { } else {
mText[columnToSet] = (char) codePoint; break;
return;
} }
}
return newCharIndex;
} else if (currentColumn > column) {
// Wide column going past end.
return currentCharIndex;
} }
}
currentCharIndex = newCharIndex;
}
}
final boolean newIsCombining = newCodePointDisplayWidth <= 0; private boolean wideDisplayCharacterStartingAt(int column) {
for (int currentCharIndex = 0, currentColumn = 0; currentCharIndex < mSpaceUsed; ) {
char c = mText[currentCharIndex++];
int codePoint = Character.isHighSurrogate(c) ? Character.toCodePoint(c, mText[currentCharIndex++]) : c;
int wcwidth = WcWidth.width(codePoint);
if (wcwidth > 0) {
if (currentColumn == column && wcwidth == 2) return true;
currentColumn += wcwidth;
if (currentColumn > column) return false;
}
}
return false;
}
boolean wasExtraColForWideChar = (columnToSet > 0) && wideDisplayCharacterStartingAt(columnToSet - 1); public void clear(long style) {
Arrays.fill(mText, ' ');
Arrays.fill(mStyle, style);
mSpaceUsed = (short) mColumns;
mHasNonOneWidthOrSurrogateChars = false;
}
if (newIsCombining) { // https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26
// When standing at second half of wide character and inserting combining: public void setChar(int columnToSet, int codePoint, long style) {
if (wasExtraColForWideChar) columnToSet--; mStyle[columnToSet] = style;
} else {
// Check if we are overwriting the second half of a wide character starting at the previous column:
if (wasExtraColForWideChar) setChar(columnToSet - 1, ' ', style);
// Check if we are overwriting the first half of a wide character starting at the next column:
boolean overwritingWideCharInNextColumn = newCodePointDisplayWidth == 2 && wideDisplayCharacterStartingAt(columnToSet + 1);
if (overwritingWideCharInNextColumn) setChar(columnToSet + 1, ' ', style);
}
char[] text = mText; final int newCodePointDisplayWidth = WcWidth.width(codePoint);
final int oldStartOfColumnIndex = findStartOfColumn(columnToSet);
final int oldCodePointDisplayWidth = WcWidth.width(text, oldStartOfColumnIndex);
// Get the number of elements in the mText array this column uses now // Fast path when we don't have any chars with width != 1
int oldCharactersUsedForColumn; if (!mHasNonOneWidthOrSurrogateChars) {
if (columnToSet + oldCodePointDisplayWidth < mColumns) { if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT || newCodePointDisplayWidth != 1) {
oldCharactersUsedForColumn = findStartOfColumn(columnToSet + oldCodePointDisplayWidth) - oldStartOfColumnIndex; mHasNonOneWidthOrSurrogateChars = true;
} else { } else {
// Last character. mText[columnToSet] = (char) codePoint;
oldCharactersUsedForColumn = mSpaceUsed - oldStartOfColumnIndex; return;
} }
// Find how many chars this column will need
int newCharactersUsedForColumn = Character.charCount(codePoint);
if (newIsCombining) {
// Combining characters are added to the contents of the column instead of overwriting them, so that they
// modify the existing contents.
// FIXME: Put a limit of combining characters.
// FIXME: Unassigned characters also get width=0.
newCharactersUsedForColumn += oldCharactersUsedForColumn;
}
int oldNextColumnIndex = oldStartOfColumnIndex + oldCharactersUsedForColumn;
int newNextColumnIndex = oldStartOfColumnIndex + newCharactersUsedForColumn;
final int javaCharDifference = newCharactersUsedForColumn - oldCharactersUsedForColumn;
if (javaCharDifference > 0) {
// Shift the rest of the line right.
int oldCharactersAfterColumn = mSpaceUsed - oldNextColumnIndex;
if (mSpaceUsed + javaCharDifference > text.length) {
// We need to grow the array
char[] newText = new char[text.length + mColumns];
System.arraycopy(text, 0, newText, 0, oldStartOfColumnIndex + oldCharactersUsedForColumn);
System.arraycopy(text, oldNextColumnIndex, newText, newNextColumnIndex, oldCharactersAfterColumn);
mText = text = newText;
} else {
System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, oldCharactersAfterColumn);
}
} else if (javaCharDifference < 0) {
// Shift the rest of the line left.
System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - oldNextColumnIndex);
}
mSpaceUsed += javaCharDifference;
// Store char. A combining character is stored at the end of the existing contents so that it modifies them:
//noinspection ResultOfMethodCallIgnored - since we already now how many java chars is used.
Character.toChars(codePoint, text, oldStartOfColumnIndex + (newIsCombining ? oldCharactersUsedForColumn : 0));
if (oldCodePointDisplayWidth == 2 && newCodePointDisplayWidth == 1) {
// Replace second half of wide char with a space. Which mean that we actually add a ' ' java character.
if (mSpaceUsed + 1 > text.length) {
char[] newText = new char[text.length + mColumns];
System.arraycopy(text, 0, newText, 0, newNextColumnIndex);
System.arraycopy(text, newNextColumnIndex, newText, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex);
mText = text = newText;
} else {
System.arraycopy(text, newNextColumnIndex, text, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex);
}
text[newNextColumnIndex] = ' ';
++mSpaceUsed;
} else if (oldCodePointDisplayWidth == 1 && newCodePointDisplayWidth == 2) {
if (columnToSet == mColumns - 1) {
throw new IllegalArgumentException("Cannot put wide character in last column");
} else if (columnToSet == mColumns - 2) {
// Truncate the line to the second part of this wide char:
mSpaceUsed = (short) newNextColumnIndex;
} else {
// Overwrite the contents of the next column, which mean we actually remove java characters. Due to the
// check at the beginning of this method we know that we are not overwriting a wide char.
int newNextNextColumnIndex = newNextColumnIndex + (Character.isHighSurrogate(mText[newNextColumnIndex]) ? 2 : 1);
int nextLen = newNextNextColumnIndex - newNextColumnIndex;
// Shift the array leftwards.
System.arraycopy(text, newNextNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - newNextNextColumnIndex);
mSpaceUsed -= nextLen;
}
}
} }
boolean isBlank() { final boolean newIsCombining = newCodePointDisplayWidth <= 0;
for (int charIndex = 0, charLen = getSpaceUsed(); charIndex < charLen; charIndex++)
if (mText[charIndex] != ' ') return false; boolean wasExtraColForWideChar = (columnToSet > 0) && wideDisplayCharacterStartingAt(columnToSet - 1);
return true;
if (newIsCombining) {
// When standing at second half of wide character and inserting combining:
if (wasExtraColForWideChar) columnToSet--;
} else {
// Check if we are overwriting the second half of a wide character starting at the previous column:
if (wasExtraColForWideChar) setChar(columnToSet - 1, ' ', style);
// Check if we are overwriting the first half of a wide character starting at the next column:
boolean overwritingWideCharInNextColumn = newCodePointDisplayWidth == 2 && wideDisplayCharacterStartingAt(columnToSet + 1);
if (overwritingWideCharInNextColumn) setChar(columnToSet + 1, ' ', style);
} }
public final long getStyle(int column) { char[] text = mText;
return mStyle[column]; final int oldStartOfColumnIndex = findStartOfColumn(columnToSet);
final int oldCodePointDisplayWidth = WcWidth.width(text, oldStartOfColumnIndex);
// Get the number of elements in the mText array this column uses now
int oldCharactersUsedForColumn;
if (columnToSet + oldCodePointDisplayWidth < mColumns) {
oldCharactersUsedForColumn = findStartOfColumn(columnToSet + oldCodePointDisplayWidth) - oldStartOfColumnIndex;
} else {
// Last character.
oldCharactersUsedForColumn = mSpaceUsed - oldStartOfColumnIndex;
} }
// Find how many chars this column will need
int newCharactersUsedForColumn = Character.charCount(codePoint);
if (newIsCombining) {
// Combining characters are added to the contents of the column instead of overwriting them, so that they
// modify the existing contents.
// FIXME: Put a limit of combining characters.
// FIXME: Unassigned characters also get width=0.
newCharactersUsedForColumn += oldCharactersUsedForColumn;
}
int oldNextColumnIndex = oldStartOfColumnIndex + oldCharactersUsedForColumn;
int newNextColumnIndex = oldStartOfColumnIndex + newCharactersUsedForColumn;
final int javaCharDifference = newCharactersUsedForColumn - oldCharactersUsedForColumn;
if (javaCharDifference > 0) {
// Shift the rest of the line right.
int oldCharactersAfterColumn = mSpaceUsed - oldNextColumnIndex;
if (mSpaceUsed + javaCharDifference > text.length) {
// We need to grow the array
char[] newText = new char[text.length + mColumns];
System.arraycopy(text, 0, newText, 0, oldStartOfColumnIndex + oldCharactersUsedForColumn);
System.arraycopy(text, oldNextColumnIndex, newText, newNextColumnIndex, oldCharactersAfterColumn);
mText = text = newText;
} else {
System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, oldCharactersAfterColumn);
}
} else if (javaCharDifference < 0) {
// Shift the rest of the line left.
System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - oldNextColumnIndex);
}
mSpaceUsed += javaCharDifference;
// Store char. A combining character is stored at the end of the existing contents so that it modifies them:
//noinspection ResultOfMethodCallIgnored - since we already now how many java chars is used.
Character.toChars(codePoint, text, oldStartOfColumnIndex + (newIsCombining ? oldCharactersUsedForColumn : 0));
if (oldCodePointDisplayWidth == 2 && newCodePointDisplayWidth == 1) {
// Replace second half of wide char with a space. Which mean that we actually add a ' ' java character.
if (mSpaceUsed + 1 > text.length) {
char[] newText = new char[text.length + mColumns];
System.arraycopy(text, 0, newText, 0, newNextColumnIndex);
System.arraycopy(text, newNextColumnIndex, newText, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex);
mText = text = newText;
} else {
System.arraycopy(text, newNextColumnIndex, text, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex);
}
text[newNextColumnIndex] = ' ';
++mSpaceUsed;
} else if (oldCodePointDisplayWidth == 1 && newCodePointDisplayWidth == 2) {
if (columnToSet == mColumns - 1) {
throw new IllegalArgumentException("Cannot put wide character in last column");
} else if (columnToSet == mColumns - 2) {
// Truncate the line to the second part of this wide char:
mSpaceUsed = (short) newNextColumnIndex;
} else {
// Overwrite the contents of the next column, which mean we actually remove java characters. Due to the
// check at the beginning of this method we know that we are not overwriting a wide char.
int newNextNextColumnIndex = newNextColumnIndex + (Character.isHighSurrogate(mText[newNextColumnIndex]) ? 2 : 1);
int nextLen = newNextNextColumnIndex - newNextColumnIndex;
// Shift the array leftwards.
System.arraycopy(text, newNextNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - newNextNextColumnIndex);
mSpaceUsed -= nextLen;
}
}
}
boolean isBlank() {
for (int charIndex = 0, charLen = getSpaceUsed(); charIndex < charLen; charIndex++)
if (mText[charIndex] != ' ') return false;
return true;
}
public final long getStyle(int column) {
return mStyle[column];
}
} }

View File

@ -8,11 +8,7 @@ import android.system.Os;
import android.system.OsConstants; import android.system.OsConstants;
import android.util.Log; import android.util.Log;
import java.io.FileDescriptor; import java.io.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.UUID; import java.util.UUID;
@ -30,322 +26,352 @@ import java.util.UUID;
*/ */
public class TerminalSession extends TerminalOutput { public class TerminalSession extends TerminalOutput {
/** Callback to be invoked when a {@link TerminalSession} changes. */ /**
public interface SessionChangedCallback { * Callback to be invoked when a {@link TerminalSession} changes.
void onTextChanged(TerminalSession changedSession); */
public interface SessionChangedCallback {
void onTextChanged(TerminalSession changedSession);
void onTitleChanged(TerminalSession changedSession); void onTitleChanged(TerminalSession changedSession);
void onSessionFinished(TerminalSession finishedSession); void onSessionFinished(TerminalSession finishedSession);
void onClipboardText(TerminalSession session, String text); void onClipboardText(TerminalSession session, String text);
void onBell(TerminalSession session); void onBell(TerminalSession session);
void onColorsChanged(TerminalSession session); void onColorsChanged(TerminalSession session);
}
@SuppressWarnings("JavaReflectionMemberAccess")
private static FileDescriptor wrapFileDescriptor(int fileDescriptor) {
FileDescriptor result = new FileDescriptor();
try {
Field descriptorField;
try {
descriptorField = FileDescriptor.class.getDeclaredField("descriptor");
} catch (NoSuchFieldException e) {
// For desktop java:
descriptorField = FileDescriptor.class.getDeclaredField("fd");
}
descriptorField.setAccessible(true);
descriptorField.set(result, fileDescriptor);
} catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) {
Log.wtf(EmulatorDebug.LOG_TAG, "Error accessing FileDescriptor#descriptor private field", e);
System.exit(1);
} }
return result;
}
@SuppressWarnings("JavaReflectionMemberAccess") private static final int MSG_NEW_INPUT = 1;
private static FileDescriptor wrapFileDescriptor(int fileDescriptor) { private static final int MSG_PROCESS_EXITED = 4;
FileDescriptor result = new FileDescriptor();
try {
Field descriptorField;
try {
descriptorField = FileDescriptor.class.getDeclaredField("descriptor");
} catch (NoSuchFieldException e) {
// For desktop java:
descriptorField = FileDescriptor.class.getDeclaredField("fd");
}
descriptorField.setAccessible(true);
descriptorField.set(result, fileDescriptor);
} catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) {
Log.wtf(EmulatorDebug.LOG_TAG, "Error accessing FileDescriptor#descriptor private field", e);
System.exit(1);
}
return result;
}
private static final int MSG_NEW_INPUT = 1; public final String mHandle = UUID.randomUUID().toString();
private static final int MSG_PROCESS_EXITED = 4;
public final String mHandle = UUID.randomUUID().toString(); private TerminalEmulator mEmulator;
private TerminalEmulator mEmulator; /**
* A queue written to from a separate thread when the process outputs, and read by main thread to process by
* terminal emulator.
*/
private final ByteQueue mProcessToTerminalIOQueue = new ByteQueue(4096);
/**
* A queue written to from the main thread due to user interaction, and read by another thread which forwards by
* writing to the {@link #mTerminalFileDescriptor}.
*/
private final ByteQueue mTerminalToProcessIOQueue = new ByteQueue(4096);
/**
* Buffer to write translate code points into utf8 before writing to mTerminalToProcessIOQueue
*/
private final byte[] mUtf8InputBuffer = new byte[5];
/** public SessionChangedCallback getSessionChangedCallback() {
* A queue written to from a separate thread when the process outputs, and read by main thread to process by return mChangeCallback;
* terminal emulator. }
*/
private final ByteQueue mProcessToTerminalIOQueue = new ByteQueue(4096);
/**
* A queue written to from the main thread due to user interaction, and read by another thread which forwards by
* writing to the {@link #mTerminalFileDescriptor}.
*/
private final ByteQueue mTerminalToProcessIOQueue = new ByteQueue(4096);
/** Buffer to write translate code points into utf8 before writing to mTerminalToProcessIOQueue */
private final byte[] mUtf8InputBuffer = new byte[5];
public SessionChangedCallback getSessionChangedCallback() { /**
return mChangeCallback; * Callback which gets notified when a session finishes or changes title.
} */
private final SessionChangedCallback mChangeCallback;
/** Callback which gets notified when a session finishes or changes title. */ /**
private final SessionChangedCallback mChangeCallback; * The pid of the executablePath process. 0 if not started and -1 if finished running.
*/
private int mShellPid;
/** The pid of the executablePath process. 0 if not started and -1 if finished running. */ /**
private int mShellPid; * The exit status of the executablePath process. Only valid if ${@link #mShellPid} is -1.
*/
private int mShellExitStatus;
/** The exit status of the executablePath process. Only valid if ${@link #mShellPid} is -1. */ /**
private int mShellExitStatus; * The file descriptor referencing the master half of a pseudo-terminal pair, resulting from calling
* {@link JNI#createSubprocess(String, String, String[], String[], int[], int, int)}.
*/
private int mTerminalFileDescriptor;
/** /**
* The file descriptor referencing the master half of a pseudo-terminal pair, resulting from calling * Set by the application for user identification of session, not by terminal.
* {@link JNI#createSubprocess(String, String, String[], String[], int[], int, int)}. */
*/ public String mSessionName;
private int mTerminalFileDescriptor;
/** Set by the application for user identification of session, not by terminal. */ @SuppressLint("HandlerLeak")
public String mSessionName; private final Handler mMainThreadHandler = new Handler() {
final byte[] mReceiveBuffer = new byte[4 * 1024];
@SuppressLint("HandlerLeak")
private final Handler mMainThreadHandler = new Handler() {
final byte[] mReceiveBuffer = new byte[4 * 1024];
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_NEW_INPUT && isRunning()) {
int bytesRead = mProcessToTerminalIOQueue.read(mReceiveBuffer, false);
if (bytesRead > 0) {
mEmulator.append(mReceiveBuffer, bytesRead);
notifyScreenUpdate();
}
} else if (msg.what == MSG_PROCESS_EXITED) {
int exitCode = (Integer) msg.obj;
cleanupResources(exitCode);
mChangeCallback.onSessionFinished(TerminalSession.this);
String exitDescription = getExitDescription(exitCode);
byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
mEmulator.append(bytesToWrite, bytesToWrite.length);
notifyScreenUpdate();
}
}
};
private final String mShellPath;
private final String mCwd;
private final String[] mArgs;
private final String[] mEnv;
public TerminalSession(String shellPath, String cwd, String[] args, String[] env, SessionChangedCallback changeCallback) {
mChangeCallback = changeCallback;
this.mShellPath = shellPath;
this.mCwd = cwd;
this.mArgs = args;
this.mEnv = env;
}
/** Inform the attached pty of the new size and reflow or initialize the emulator. */
public void updateSize(int columns, int rows) {
if (mEmulator == null) {
initializeEmulator(columns, rows);
} else {
JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns);
mEmulator.resize(columns, rows);
}
}
/** The terminal title as set through escape sequences or null if none set. */
public String getTitle() {
return (mEmulator == null) ? null : mEmulator.getTitle();
}
/**
* Set the terminal emulator's window size and start terminal emulation.
*
* @param columns The number of columns in the terminal window.
* @param rows The number of rows in the terminal window.
*/
public void initializeEmulator(int columns, int rows) {
mEmulator = new TerminalEmulator(this, columns, rows, /* transcript= */2000);
int[] processId = new int[1];
mTerminalFileDescriptor = JNI.createSubprocess(mShellPath, mCwd, mArgs, mEnv, processId, rows, columns);
mShellPid = processId[0];
final FileDescriptor terminalFileDescriptorWrapped = wrapFileDescriptor(mTerminalFileDescriptor);
new Thread("TermSessionInputReader[pid=" + mShellPid + "]") {
@Override
public void run() {
try (InputStream termIn = new FileInputStream(terminalFileDescriptorWrapped)) {
final byte[] buffer = new byte[4096];
while (true) {
int read = termIn.read(buffer);
if (read == -1) return;
if (!mProcessToTerminalIOQueue.write(buffer, 0, read)) return;
mMainThreadHandler.sendEmptyMessage(MSG_NEW_INPUT);
}
} catch (Exception e) {
// Ignore, just shutting down.
}
}
}.start();
new Thread("TermSessionOutputWriter[pid=" + mShellPid + "]") {
@Override
public void run() {
final byte[] buffer = new byte[4096];
try (FileOutputStream termOut = new FileOutputStream(terminalFileDescriptorWrapped)) {
while (true) {
int bytesToWrite = mTerminalToProcessIOQueue.read(buffer, true);
if (bytesToWrite == -1) return;
termOut.write(buffer, 0, bytesToWrite);
}
} catch (IOException e) {
// Ignore.
}
}
}.start();
new Thread("TermSessionWaiter[pid=" + mShellPid + "]") {
@Override
public void run() {
int processExitCode = JNI.waitFor(mShellPid);
mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(MSG_PROCESS_EXITED, processExitCode));
}
}.start();
}
/** Write data to the executablePath process. */
@Override @Override
public void write(byte[] data, int offset, int count) { public void handleMessage(Message msg) {
if (mShellPid > 0) mTerminalToProcessIOQueue.write(data, offset, count); if (msg.what == MSG_NEW_INPUT && isRunning()) {
} int bytesRead = mProcessToTerminalIOQueue.read(mReceiveBuffer, false);
if (bytesRead > 0) {
/** Write the Unicode code point to the terminal encoded in UTF-8. */ mEmulator.append(mReceiveBuffer, bytesRead);
public void writeCodePoint(boolean prependEscape, int codePoint) { notifyScreenUpdate();
if (codePoint > 1114111 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
// 1114111 (= 2**16 + 1024**2 - 1) is the highest code point, [0xD800,0xDFFF] is the surrogate range.
throw new IllegalArgumentException("Invalid code point: " + codePoint);
} }
} else if (msg.what == MSG_PROCESS_EXITED) {
int exitCode = (Integer) msg.obj;
cleanupResources(exitCode);
mChangeCallback.onSessionFinished(TerminalSession.this);
int bufferPosition = 0; String exitDescription = getExitDescription(exitCode);
if (prependEscape) mUtf8InputBuffer[bufferPosition++] = 27; byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
mEmulator.append(bytesToWrite, bytesToWrite.length);
if (codePoint <= /* 7 bits */0b1111111) {
mUtf8InputBuffer[bufferPosition++] = (byte) codePoint;
} else if (codePoint <= /* 11 bits */0b11111111111) {
/* 110xxxxx leading byte with leading 5 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b11000000 | (codePoint >> 6));
/* 10xxxxxx continuation byte with following 6 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111));
} else if (codePoint <= /* 16 bits */0b1111111111111111) {
/* 1110xxxx leading byte with leading 4 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b11100000 | (codePoint >> 12));
/* 10xxxxxx continuation byte with following 6 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111));
/* 10xxxxxx continuation byte with following 6 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111));
} else { /* We have checked codePoint <= 1114111 above, so we have max 21 bits = 0b111111111111111111111 */
/* 11110xxx leading byte with leading 3 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b11110000 | (codePoint >> 18));
/* 10xxxxxx continuation byte with following 6 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 12) & 0b111111));
/* 10xxxxxx continuation byte with following 6 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111));
/* 10xxxxxx continuation byte with following 6 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111));
}
write(mUtf8InputBuffer, 0, bufferPosition);
}
public TerminalEmulator getEmulator() {
return mEmulator;
}
/** Notify the {@link #mChangeCallback} that the screen has changed. */
private void notifyScreenUpdate() {
mChangeCallback.onTextChanged(this);
}
/** Reset state for terminal emulator state. */
public void reset() {
mEmulator.reset();
notifyScreenUpdate(); notifyScreenUpdate();
}
} }
};
/** Finish this terminal session by sending SIGKILL to the executablePath. */ private final String mShellPath;
public void finishIfRunning() { private final String mCwd;
if (isRunning()) { private final String[] mArgs;
try { private final String[] mEnv;
Os.kill(mShellPid, OsConstants.SIGKILL);
} catch (ErrnoException e) { public TerminalSession(String shellPath, String cwd, String[] args, String[] env, SessionChangedCallback changeCallback) {
Log.w("neoterm-shell-session", mChangeCallback = changeCallback;
"Failed sending SIGKILL: " + e.getMessage());
} this.mShellPath = shellPath;
this.mCwd = cwd;
this.mArgs = args;
this.mEnv = env;
}
/**
* Inform the attached pty of the new size and reflow or initialize the emulator.
*/
public void updateSize(int columns, int rows) {
if (mEmulator == null) {
initializeEmulator(columns, rows);
} else {
JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns);
mEmulator.resize(columns, rows);
}
}
/**
* The terminal title as set through escape sequences or null if none set.
*/
public String getTitle() {
return (mEmulator == null) ? null : mEmulator.getTitle();
}
/**
* Set the terminal emulator's window size and start terminal emulation.
*
* @param columns The number of columns in the terminal window.
* @param rows The number of rows in the terminal window.
*/
public void initializeEmulator(int columns, int rows) {
mEmulator = new TerminalEmulator(this, columns, rows, /* transcript= */2000);
int[] processId = new int[1];
mTerminalFileDescriptor = JNI.createSubprocess(mShellPath, mCwd, mArgs, mEnv, processId, rows, columns);
mShellPid = processId[0];
final FileDescriptor terminalFileDescriptorWrapped = wrapFileDescriptor(mTerminalFileDescriptor);
new Thread("TermSessionInputReader[pid=" + mShellPid + "]") {
@Override
public void run() {
try (InputStream termIn = new FileInputStream(terminalFileDescriptorWrapped)) {
final byte[] buffer = new byte[4096];
while (true) {
int read = termIn.read(buffer);
if (read == -1) return;
if (!mProcessToTerminalIOQueue.write(buffer, 0, read)) return;
mMainThreadHandler.sendEmptyMessage(MSG_NEW_INPUT);
}
} catch (Exception e) {
// Ignore, just shutting down.
} }
} }
}.start();
protected String getExitDescription(int exitCode) { new Thread("TermSessionOutputWriter[pid=" + mShellPid + "]") {
String exitDescription = "\r\n[Process completed"; @Override
if (exitCode > 0) { public void run() {
// Non-zero process exit. final byte[] buffer = new byte[4096];
exitDescription += " (code " + exitCode + ")"; try (FileOutputStream termOut = new FileOutputStream(terminalFileDescriptorWrapped)) {
} else if (exitCode < 0) { while (true) {
// Negated signal. int bytesToWrite = mTerminalToProcessIOQueue.read(buffer, true);
exitDescription += " (signal " + (-exitCode) + ")"; if (bytesToWrite == -1) return;
termOut.write(buffer, 0, bytesToWrite);
}
} catch (IOException e) {
// Ignore.
} }
exitDescription += " - press Enter]"; }
return exitDescription; }.start();
new Thread("TermSessionWaiter[pid=" + mShellPid + "]") {
@Override
public void run() {
int processExitCode = JNI.waitFor(mShellPid);
mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(MSG_PROCESS_EXITED, processExitCode));
}
}.start();
}
/**
* Write data to the executablePath process.
*/
@Override
public void write(byte[] data, int offset, int count) {
if (mShellPid > 0) mTerminalToProcessIOQueue.write(data, offset, count);
}
/**
* Write the Unicode code point to the terminal encoded in UTF-8.
*/
public void writeCodePoint(boolean prependEscape, int codePoint) {
if (codePoint > 1114111 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
// 1114111 (= 2**16 + 1024**2 - 1) is the highest code point, [0xD800,0xDFFF] is the surrogate range.
throw new IllegalArgumentException("Invalid code point: " + codePoint);
} }
/** Cleanup resources when the process exits. */ int bufferPosition = 0;
private void cleanupResources(int exitStatus) { if (prependEscape) mUtf8InputBuffer[bufferPosition++] = 27;
synchronized (this) {
mShellPid = -1;
mShellExitStatus = exitStatus;
}
// Stop the reader and writer threads, and close the I/O streams if (codePoint <= /* 7 bits */0b1111111) {
mTerminalToProcessIOQueue.close(); mUtf8InputBuffer[bufferPosition++] = (byte) codePoint;
mProcessToTerminalIOQueue.close(); } else if (codePoint <= /* 11 bits */0b11111111111) {
JNI.close(mTerminalFileDescriptor); /* 110xxxxx leading byte with leading 5 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b11000000 | (codePoint >> 6));
/* 10xxxxxx continuation byte with following 6 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111));
} else if (codePoint <= /* 16 bits */0b1111111111111111) {
/* 1110xxxx leading byte with leading 4 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b11100000 | (codePoint >> 12));
/* 10xxxxxx continuation byte with following 6 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111));
/* 10xxxxxx continuation byte with following 6 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111));
} else { /* We have checked codePoint <= 1114111 above, so we have max 21 bits = 0b111111111111111111111 */
/* 11110xxx leading byte with leading 3 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b11110000 | (codePoint >> 18));
/* 10xxxxxx continuation byte with following 6 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 12) & 0b111111));
/* 10xxxxxx continuation byte with following 6 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111));
/* 10xxxxxx continuation byte with following 6 bits */
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111));
}
write(mUtf8InputBuffer, 0, bufferPosition);
}
public TerminalEmulator getEmulator() {
return mEmulator;
}
/**
* Notify the {@link #mChangeCallback} that the screen has changed.
*/
private void notifyScreenUpdate() {
mChangeCallback.onTextChanged(this);
}
/**
* Reset state for terminal emulator state.
*/
public void reset() {
mEmulator.reset();
notifyScreenUpdate();
}
/**
* Finish this terminal session by sending SIGKILL to the executablePath.
*/
public void finishIfRunning() {
if (isRunning()) {
try {
Os.kill(mShellPid, OsConstants.SIGKILL);
} catch (ErrnoException e) {
Log.w("neoterm-shell-session",
"Failed sending SIGKILL: " + e.getMessage());
}
}
}
protected String getExitDescription(int exitCode) {
String exitDescription = "\r\n[Process completed";
if (exitCode > 0) {
// Non-zero process exit.
exitDescription += " (code " + exitCode + ")";
} else if (exitCode < 0) {
// Negated signal.
exitDescription += " (signal " + (-exitCode) + ")";
}
exitDescription += " - press Enter]";
return exitDescription;
}
/**
* Cleanup resources when the process exits.
*/
private void cleanupResources(int exitStatus) {
synchronized (this) {
mShellPid = -1;
mShellExitStatus = exitStatus;
} }
@Override // Stop the reader and writer threads, and close the I/O streams
public void titleChanged(String oldTitle, String newTitle) { mTerminalToProcessIOQueue.close();
mChangeCallback.onTitleChanged(this); mProcessToTerminalIOQueue.close();
} JNI.close(mTerminalFileDescriptor);
}
public synchronized boolean isRunning() { @Override
return mShellPid != -1; public void titleChanged(String oldTitle, String newTitle) {
} mChangeCallback.onTitleChanged(this);
}
/** Only valid if not {@link #isRunning()}. */ public synchronized boolean isRunning() {
public synchronized int getExitStatus() { return mShellPid != -1;
return mShellExitStatus; }
}
@Override /**
public void clipboardText(String text) { * Only valid if not {@link #isRunning()}.
mChangeCallback.onClipboardText(this, text); */
} public synchronized int getExitStatus() {
return mShellExitStatus;
}
@Override @Override
public void onBell() { public void clipboardText(String text) {
mChangeCallback.onBell(this); mChangeCallback.onClipboardText(this, text);
} }
@Override @Override
public void onColorsChanged() { public void onBell() {
mChangeCallback.onColorsChanged(this); mChangeCallback.onBell(this);
} }
public int getPid() { @Override
return mShellPid; public void onColorsChanged() {
} mChangeCallback.onColorsChanged(this);
}
public int getPid() {
return mShellPid;
}
} }

View File

@ -14,77 +14,87 @@ package io.neoterm.backend;
*/ */
public final class TextStyle { public final class TextStyle {
public final static int CHARACTER_ATTRIBUTE_BOLD = 1; public final static int CHARACTER_ATTRIBUTE_BOLD = 1;
public final static int CHARACTER_ATTRIBUTE_ITALIC = 1 << 1; public final static int CHARACTER_ATTRIBUTE_ITALIC = 1 << 1;
public final static int CHARACTER_ATTRIBUTE_UNDERLINE = 1 << 2; public final static int CHARACTER_ATTRIBUTE_UNDERLINE = 1 << 2;
public final static int CHARACTER_ATTRIBUTE_BLINK = 1 << 3; public final static int CHARACTER_ATTRIBUTE_BLINK = 1 << 3;
public final static int CHARACTER_ATTRIBUTE_INVERSE = 1 << 4; public final static int CHARACTER_ATTRIBUTE_INVERSE = 1 << 4;
public final static int CHARACTER_ATTRIBUTE_INVISIBLE = 1 << 5; public final static int CHARACTER_ATTRIBUTE_INVISIBLE = 1 << 5;
public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6; public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6;
/** /**
* The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable. * The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable.
* <p> * <p>
* This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that * This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that
* come after it as erasable from the screen. * come after it as erasable from the screen.
* </p> * </p>
*/ */
public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7; public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7;
/** Dim colors. Also known as faint or half intensity. */ /**
public final static int CHARACTER_ATTRIBUTE_DIM = 1 << 8; * Dim colors. Also known as faint or half intensity.
/** If true (24-bit) color is used for the cell for foregroundColor. */ */
private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND = 1 << 9; public final static int CHARACTER_ATTRIBUTE_DIM = 1 << 8;
/** If true (24-bit) color is used for the cell for foregroundColor. */ /**
private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND= 1 << 10; * If true (24-bit) color is used for the cell for foregroundColor.
*/
private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND = 1 << 9;
/**
* If true (24-bit) color is used for the cell for foregroundColor.
*/
private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND = 1 << 10;
public final static int COLOR_INDEX_FOREGROUND = 256; public final static int COLOR_INDEX_FOREGROUND = 256;
public final static int COLOR_INDEX_BACKGROUND = 257; public final static int COLOR_INDEX_BACKGROUND = 257;
public final static int COLOR_INDEX_CURSOR = 258; public final static int COLOR_INDEX_CURSOR = 258;
/** The 256 standard color entries and the three special (foregroundColor, backgroundColor and cursorColor) ones. */ /**
public final static int NUM_INDEXED_COLORS = 259; * The 256 standard color entries and the three special (foregroundColor, backgroundColor and cursorColor) ones.
*/
public final static int NUM_INDEXED_COLORS = 259;
/** Normal foregroundColor and backgroundColor colors and no effects. */ /**
final static long NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0); * Normal foregroundColor and backgroundColor colors and no effects.
*/
final static long NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0);
static long encode(int foreColor, int backColor, int effect) { static long encode(int foreColor, int backColor, int effect) {
long result = effect & 0b111111111; long result = effect & 0b111111111;
if ((0xff000000 & foreColor) == 0xff000000) { if ((0xff000000 & foreColor) == 0xff000000) {
// 24-bit color. // 24-bit color.
result |= CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND | ((foreColor & 0x00ffffffL) << 40L); result |= CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND | ((foreColor & 0x00ffffffL) << 40L);
} else { } else {
// Indexed color. // Indexed color.
result |= (foreColor & 0b111111111L) << 40; result |= (foreColor & 0b111111111L) << 40;
} }
if ((0xff000000 & backColor) == 0xff000000) { if ((0xff000000 & backColor) == 0xff000000) {
// 24-bit color. // 24-bit color.
result |= CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND | ((backColor & 0x00ffffffL) << 16L); result |= CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND | ((backColor & 0x00ffffffL) << 16L);
} else { } else {
// Indexed color. // Indexed color.
result |= (backColor & 0b111111111L) << 16L; result |= (backColor & 0b111111111L) << 16L;
}
return result;
} }
public static int decodeForeColor(long style) { return result;
if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND) == 0) { }
return (int) ((style >>> 40) & 0b111111111L);
} else {
return 0xff000000 | (int) ((style >>> 40) & 0x00ffffffL);
}
public static int decodeForeColor(long style) {
if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND) == 0) {
return (int) ((style >>> 40) & 0b111111111L);
} else {
return 0xff000000 | (int) ((style >>> 40) & 0x00ffffffL);
} }
public static int decodeBackColor(long style) { }
if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND) == 0) {
return (int) ((style >>> 16) & 0b111111111L);
} else {
return 0xff000000 | (int) ((style >>> 16) & 0x00ffffffL);
}
}
public static int decodeEffect(long style) { public static int decodeBackColor(long style) {
return (int) (style & 0b11111111111); if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND) == 0) {
return (int) ((style >>> 16) & 0b111111111L);
} else {
return 0xff000000 | (int) ((style >>> 16) & 0x00ffffffL);
} }
}
public static int decodeEffect(long style) {
return (int) (style & 0b11111111111);
}
} }

View File

@ -2,457 +2,461 @@ package io.neoterm.backend;
/** /**
* Implementation of wcwidth(3) for Unicode 9. * Implementation of wcwidth(3) for Unicode 9.
* * <p>
* Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters. * Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters.
*/ */
public final class WcWidth { public final class WcWidth {
// From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py // From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py
// t commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02): // t commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02):
private static final int[][] ZERO_WIDTH = { private static final int[][] ZERO_WIDTH = {
{0x0300, 0x036f}, // Combining Grave Accent ..Combining Latin Small Le {0x0300, 0x036f}, // Combining Grave Accent ..Combining Latin Small Le
{0x0483, 0x0489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli {0x0483, 0x0489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli
{0x0591, 0x05bd}, // Hebrew Accent Etnahta ..Hebrew Point Meteg {0x0591, 0x05bd}, // Hebrew Accent Etnahta ..Hebrew Point Meteg
{0x05bf, 0x05bf}, // Hebrew Point Rafe ..Hebrew Point Rafe {0x05bf, 0x05bf}, // Hebrew Point Rafe ..Hebrew Point Rafe
{0x05c1, 0x05c2}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot {0x05c1, 0x05c2}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot
{0x05c4, 0x05c5}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot {0x05c4, 0x05c5}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot
{0x05c7, 0x05c7}, // Hebrew Point Qamats Qata..Hebrew Point Qamats Qata {0x05c7, 0x05c7}, // Hebrew Point Qamats Qata..Hebrew Point Qamats Qata
{0x0610, 0x061a}, // Arabic Sign Sallallahou ..Arabic Small Kasra {0x0610, 0x061a}, // Arabic Sign Sallallahou ..Arabic Small Kasra
{0x064b, 0x065f}, // Arabic Fathatan ..Arabic Wavy Hamza Below {0x064b, 0x065f}, // Arabic Fathatan ..Arabic Wavy Hamza Below
{0x0670, 0x0670}, // Arabic Letter Superscrip..Arabic Letter Superscrip {0x0670, 0x0670}, // Arabic Letter Superscrip..Arabic Letter Superscrip
{0x06d6, 0x06dc}, // Arabic Small High Ligatu..Arabic Small High Seen {0x06d6, 0x06dc}, // Arabic Small High Ligatu..Arabic Small High Seen
{0x06df, 0x06e4}, // Arabic Small High Rounde..Arabic Small High Madda {0x06df, 0x06e4}, // Arabic Small High Rounde..Arabic Small High Madda
{0x06e7, 0x06e8}, // Arabic Small High Yeh ..Arabic Small High Noon {0x06e7, 0x06e8}, // Arabic Small High Yeh ..Arabic Small High Noon
{0x06ea, 0x06ed}, // Arabic Empty Centre Low ..Arabic Small Low Meem {0x06ea, 0x06ed}, // Arabic Empty Centre Low ..Arabic Small Low Meem
{0x0711, 0x0711}, // Syriac Letter Superscrip..Syriac Letter Superscrip {0x0711, 0x0711}, // Syriac Letter Superscrip..Syriac Letter Superscrip
{0x0730, 0x074a}, // Syriac Pthaha Above ..Syriac Barrekh {0x0730, 0x074a}, // Syriac Pthaha Above ..Syriac Barrekh
{0x07a6, 0x07b0}, // Thaana Abafili ..Thaana Sukun {0x07a6, 0x07b0}, // Thaana Abafili ..Thaana Sukun
{0x07eb, 0x07f3}, // Nko Combining Sh||t High..Nko Combining Double Dot {0x07eb, 0x07f3}, // Nko Combining Sh||t High..Nko Combining Double Dot
{0x0816, 0x0819}, // Samaritan Mark In ..Samaritan Mark Dagesh {0x0816, 0x0819}, // Samaritan Mark In ..Samaritan Mark Dagesh
{0x081b, 0x0823}, // Samaritan Mark Epentheti..Samaritan Vowel Sign A {0x081b, 0x0823}, // Samaritan Mark Epentheti..Samaritan Vowel Sign A
{0x0825, 0x0827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U {0x0825, 0x0827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U
{0x0829, 0x082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa {0x0829, 0x082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa
{0x0859, 0x085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark {0x0859, 0x085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark
{0x08d4, 0x08e1}, // (nil) .. {0x08d4, 0x08e1}, // (nil) ..
{0x08e3, 0x0902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara {0x08e3, 0x0902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara
{0x093a, 0x093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe {0x093a, 0x093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe
{0x093c, 0x093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta {0x093c, 0x093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta
{0x0941, 0x0948}, // Devanagari Vowel Sign U ..Devanagari Vowel Sign Ai {0x0941, 0x0948}, // Devanagari Vowel Sign U ..Devanagari Vowel Sign Ai
{0x094d, 0x094d}, // Devanagari Sign Virama ..Devanagari Sign Virama {0x094d, 0x094d}, // Devanagari Sign Virama ..Devanagari Sign Virama
{0x0951, 0x0957}, // Devanagari Stress Sign U..Devanagari Vowel Sign Uu {0x0951, 0x0957}, // Devanagari Stress Sign U..Devanagari Vowel Sign Uu
{0x0962, 0x0963}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo {0x0962, 0x0963}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo
{0x0981, 0x0981}, // Bengali Sign Candrabindu..Bengali Sign Candrabindu {0x0981, 0x0981}, // Bengali Sign Candrabindu..Bengali Sign Candrabindu
{0x09bc, 0x09bc}, // Bengali Sign Nukta ..Bengali Sign Nukta {0x09bc, 0x09bc}, // Bengali Sign Nukta ..Bengali Sign Nukta
{0x09c1, 0x09c4}, // Bengali Vowel Sign U ..Bengali Vowel Sign Vocal {0x09c1, 0x09c4}, // Bengali Vowel Sign U ..Bengali Vowel Sign Vocal
{0x09cd, 0x09cd}, // Bengali Sign Virama ..Bengali Sign Virama {0x09cd, 0x09cd}, // Bengali Sign Virama ..Bengali Sign Virama
{0x09e2, 0x09e3}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal {0x09e2, 0x09e3}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal
{0x0a01, 0x0a02}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Bindi {0x0a01, 0x0a02}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Bindi
{0x0a3c, 0x0a3c}, // Gurmukhi Sign Nukta ..Gurmukhi Sign Nukta {0x0a3c, 0x0a3c}, // Gurmukhi Sign Nukta ..Gurmukhi Sign Nukta
{0x0a41, 0x0a42}, // Gurmukhi Vowel Sign U ..Gurmukhi Vowel Sign Uu {0x0a41, 0x0a42}, // Gurmukhi Vowel Sign U ..Gurmukhi Vowel Sign Uu
{0x0a47, 0x0a48}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai {0x0a47, 0x0a48}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai
{0x0a4b, 0x0a4d}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama {0x0a4b, 0x0a4d}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama
{0x0a51, 0x0a51}, // Gurmukhi Sign Udaat ..Gurmukhi Sign Udaat {0x0a51, 0x0a51}, // Gurmukhi Sign Udaat ..Gurmukhi Sign Udaat
{0x0a70, 0x0a71}, // Gurmukhi Tippi ..Gurmukhi Addak {0x0a70, 0x0a71}, // Gurmukhi Tippi ..Gurmukhi Addak
{0x0a75, 0x0a75}, // Gurmukhi Sign Yakash ..Gurmukhi Sign Yakash {0x0a75, 0x0a75}, // Gurmukhi Sign Yakash ..Gurmukhi Sign Yakash
{0x0a81, 0x0a82}, // Gujarati Sign Candrabind..Gujarati Sign Anusvara {0x0a81, 0x0a82}, // Gujarati Sign Candrabind..Gujarati Sign Anusvara
{0x0abc, 0x0abc}, // Gujarati Sign Nukta ..Gujarati Sign Nukta {0x0abc, 0x0abc}, // Gujarati Sign Nukta ..Gujarati Sign Nukta
{0x0ac1, 0x0ac5}, // Gujarati Vowel Sign U ..Gujarati Vowel Sign Cand {0x0ac1, 0x0ac5}, // Gujarati Vowel Sign U ..Gujarati Vowel Sign Cand
{0x0ac7, 0x0ac8}, // Gujarati Vowel Sign E ..Gujarati Vowel Sign Ai {0x0ac7, 0x0ac8}, // Gujarati Vowel Sign E ..Gujarati Vowel Sign Ai
{0x0acd, 0x0acd}, // Gujarati Sign Virama ..Gujarati Sign Virama {0x0acd, 0x0acd}, // Gujarati Sign Virama ..Gujarati Sign Virama
{0x0ae2, 0x0ae3}, // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca {0x0ae2, 0x0ae3}, // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca
{0x0b01, 0x0b01}, // ||iya Sign Candrabindu ..||iya Sign Candrabindu {0x0b01, 0x0b01}, // ||iya Sign Candrabindu ..||iya Sign Candrabindu
{0x0b3c, 0x0b3c}, // ||iya Sign Nukta ..||iya Sign Nukta {0x0b3c, 0x0b3c}, // ||iya Sign Nukta ..||iya Sign Nukta
{0x0b3f, 0x0b3f}, // ||iya Vowel Sign I ..||iya Vowel Sign I {0x0b3f, 0x0b3f}, // ||iya Vowel Sign I ..||iya Vowel Sign I
{0x0b41, 0x0b44}, // ||iya Vowel Sign U ..||iya Vowel Sign Vocalic {0x0b41, 0x0b44}, // ||iya Vowel Sign U ..||iya Vowel Sign Vocalic
{0x0b4d, 0x0b4d}, // ||iya Sign Virama ..||iya Sign Virama {0x0b4d, 0x0b4d}, // ||iya Sign Virama ..||iya Sign Virama
{0x0b56, 0x0b56}, // ||iya Ai Length Mark ..||iya Ai Length Mark {0x0b56, 0x0b56}, // ||iya Ai Length Mark ..||iya Ai Length Mark
{0x0b62, 0x0b63}, // ||iya Vowel Sign Vocalic..||iya Vowel Sign Vocalic {0x0b62, 0x0b63}, // ||iya Vowel Sign Vocalic..||iya Vowel Sign Vocalic
{0x0b82, 0x0b82}, // Tamil Sign Anusvara ..Tamil Sign Anusvara {0x0b82, 0x0b82}, // Tamil Sign Anusvara ..Tamil Sign Anusvara
{0x0bc0, 0x0bc0}, // Tamil Vowel Sign Ii ..Tamil Vowel Sign Ii {0x0bc0, 0x0bc0}, // Tamil Vowel Sign Ii ..Tamil Vowel Sign Ii
{0x0bcd, 0x0bcd}, // Tamil Sign Virama ..Tamil Sign Virama {0x0bcd, 0x0bcd}, // Tamil Sign Virama ..Tamil Sign Virama
{0x0c00, 0x0c00}, // Telugu Sign Combining Ca..Telugu Sign Combining Ca {0x0c00, 0x0c00}, // Telugu Sign Combining Ca..Telugu Sign Combining Ca
{0x0c3e, 0x0c40}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Ii {0x0c3e, 0x0c40}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Ii
{0x0c46, 0x0c48}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai {0x0c46, 0x0c48}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai
{0x0c4a, 0x0c4d}, // Telugu Vowel Sign O ..Telugu Sign Virama {0x0c4a, 0x0c4d}, // Telugu Vowel Sign O ..Telugu Sign Virama
{0x0c55, 0x0c56}, // Telugu Length Mark ..Telugu Ai Length Mark {0x0c55, 0x0c56}, // Telugu Length Mark ..Telugu Ai Length Mark
{0x0c62, 0x0c63}, // Telugu Vowel Sign Vocali..Telugu Vowel Sign Vocali {0x0c62, 0x0c63}, // Telugu Vowel Sign Vocali..Telugu Vowel Sign Vocali
{0x0c81, 0x0c81}, // Kannada Sign Candrabindu..Kannada Sign Candrabindu {0x0c81, 0x0c81}, // Kannada Sign Candrabindu..Kannada Sign Candrabindu
{0x0cbc, 0x0cbc}, // Kannada Sign Nukta ..Kannada Sign Nukta {0x0cbc, 0x0cbc}, // Kannada Sign Nukta ..Kannada Sign Nukta
{0x0cbf, 0x0cbf}, // Kannada Vowel Sign I ..Kannada Vowel Sign I {0x0cbf, 0x0cbf}, // Kannada Vowel Sign I ..Kannada Vowel Sign I
{0x0cc6, 0x0cc6}, // Kannada Vowel Sign E ..Kannada Vowel Sign E {0x0cc6, 0x0cc6}, // Kannada Vowel Sign E ..Kannada Vowel Sign E
{0x0ccc, 0x0ccd}, // Kannada Vowel Sign Au ..Kannada Sign Virama {0x0ccc, 0x0ccd}, // Kannada Vowel Sign Au ..Kannada Sign Virama
{0x0ce2, 0x0ce3}, // Kannada Vowel Sign Vocal..Kannada Vowel Sign Vocal {0x0ce2, 0x0ce3}, // Kannada Vowel Sign Vocal..Kannada Vowel Sign Vocal
{0x0d01, 0x0d01}, // Malayalam Sign Candrabin..Malayalam Sign Candrabin {0x0d01, 0x0d01}, // Malayalam Sign Candrabin..Malayalam Sign Candrabin
{0x0d41, 0x0d44}, // Malayalam Vowel Sign U ..Malayalam Vowel Sign Voc {0x0d41, 0x0d44}, // Malayalam Vowel Sign U ..Malayalam Vowel Sign Voc
{0x0d4d, 0x0d4d}, // Malayalam Sign Virama ..Malayalam Sign Virama {0x0d4d, 0x0d4d}, // Malayalam Sign Virama ..Malayalam Sign Virama
{0x0d62, 0x0d63}, // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc {0x0d62, 0x0d63}, // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc
{0x0dca, 0x0dca}, // Sinhala Sign Al-lakuna ..Sinhala Sign Al-lakuna {0x0dca, 0x0dca}, // Sinhala Sign Al-lakuna ..Sinhala Sign Al-lakuna
{0x0dd2, 0x0dd4}, // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti {0x0dd2, 0x0dd4}, // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti
{0x0dd6, 0x0dd6}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga {0x0dd6, 0x0dd6}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga
{0x0e31, 0x0e31}, // Thai Character Mai Han-a..Thai Character Mai Han-a {0x0e31, 0x0e31}, // Thai Character Mai Han-a..Thai Character Mai Han-a
{0x0e34, 0x0e3a}, // Thai Character Sara I ..Thai Character Phinthu {0x0e34, 0x0e3a}, // Thai Character Sara I ..Thai Character Phinthu
{0x0e47, 0x0e4e}, // Thai Character Maitaikhu..Thai Character Yamakkan {0x0e47, 0x0e4e}, // Thai Character Maitaikhu..Thai Character Yamakkan
{0x0eb1, 0x0eb1}, // Lao Vowel Sign Mai Kan ..Lao Vowel Sign Mai Kan {0x0eb1, 0x0eb1}, // Lao Vowel Sign Mai Kan ..Lao Vowel Sign Mai Kan
{0x0eb4, 0x0eb9}, // Lao Vowel Sign I ..Lao Vowel Sign Uu {0x0eb4, 0x0eb9}, // Lao Vowel Sign I ..Lao Vowel Sign Uu
{0x0ebb, 0x0ebc}, // Lao Vowel Sign Mai Kon ..Lao Semivowel Sign Lo {0x0ebb, 0x0ebc}, // Lao Vowel Sign Mai Kon ..Lao Semivowel Sign Lo
{0x0ec8, 0x0ecd}, // Lao Tone Mai Ek ..Lao Niggahita {0x0ec8, 0x0ecd}, // Lao Tone Mai Ek ..Lao Niggahita
{0x0f18, 0x0f19}, // Tibetan Astrological Sig..Tibetan Astrological Sig {0x0f18, 0x0f19}, // Tibetan Astrological Sig..Tibetan Astrological Sig
{0x0f35, 0x0f35}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung {0x0f35, 0x0f35}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
{0x0f37, 0x0f37}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung {0x0f37, 0x0f37}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
{0x0f39, 0x0f39}, // Tibetan Mark Tsa -phru ..Tibetan Mark Tsa -phru {0x0f39, 0x0f39}, // Tibetan Mark Tsa -phru ..Tibetan Mark Tsa -phru
{0x0f71, 0x0f7e}, // Tibetan Vowel Sign Aa ..Tibetan Sign Rjes Su Nga {0x0f71, 0x0f7e}, // Tibetan Vowel Sign Aa ..Tibetan Sign Rjes Su Nga
{0x0f80, 0x0f84}, // Tibetan Vowel Sign Rever..Tibetan Mark Halanta {0x0f80, 0x0f84}, // Tibetan Vowel Sign Rever..Tibetan Mark Halanta
{0x0f86, 0x0f87}, // Tibetan Sign Lci Rtags ..Tibetan Sign Yang Rtags {0x0f86, 0x0f87}, // Tibetan Sign Lci Rtags ..Tibetan Sign Yang Rtags
{0x0f8d, 0x0f97}, // Tibetan Subjoined Sign L..Tibetan Subjoined Letter {0x0f8d, 0x0f97}, // Tibetan Subjoined Sign L..Tibetan Subjoined Letter
{0x0f99, 0x0fbc}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter {0x0f99, 0x0fbc}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter
{0x0fc6, 0x0fc6}, // Tibetan Symbol Padma Gda..Tibetan Symbol Padma Gda {0x0fc6, 0x0fc6}, // Tibetan Symbol Padma Gda..Tibetan Symbol Padma Gda
{0x102d, 0x1030}, // Myanmar Vowel Sign I ..Myanmar Vowel Sign Uu {0x102d, 0x1030}, // Myanmar Vowel Sign I ..Myanmar Vowel Sign Uu
{0x1032, 0x1037}, // Myanmar Vowel Sign Ai ..Myanmar Sign Dot Below {0x1032, 0x1037}, // Myanmar Vowel Sign Ai ..Myanmar Sign Dot Below
{0x1039, 0x103a}, // Myanmar Sign Virama ..Myanmar Sign Asat {0x1039, 0x103a}, // Myanmar Sign Virama ..Myanmar Sign Asat
{0x103d, 0x103e}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M {0x103d, 0x103e}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M
{0x1058, 0x1059}, // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal {0x1058, 0x1059}, // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal
{0x105e, 0x1060}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M {0x105e, 0x1060}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M
{0x1071, 0x1074}, // Myanmar Vowel Sign Geba ..Myanmar Vowel Sign Kayah {0x1071, 0x1074}, // Myanmar Vowel Sign Geba ..Myanmar Vowel Sign Kayah
{0x1082, 0x1082}, // Myanmar Consonant Sign S..Myanmar Consonant Sign S {0x1082, 0x1082}, // Myanmar Consonant Sign S..Myanmar Consonant Sign S
{0x1085, 0x1086}, // Myanmar Vowel Sign Shan ..Myanmar Vowel Sign Shan {0x1085, 0x1086}, // Myanmar Vowel Sign Shan ..Myanmar Vowel Sign Shan
{0x108d, 0x108d}, // Myanmar Sign Shan Counci..Myanmar Sign Shan Counci {0x108d, 0x108d}, // Myanmar Sign Shan Counci..Myanmar Sign Shan Counci
{0x109d, 0x109d}, // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton {0x109d, 0x109d}, // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton
{0x135d, 0x135f}, // Ethiopic Combining Gemin..Ethiopic Combining Gemin {0x135d, 0x135f}, // Ethiopic Combining Gemin..Ethiopic Combining Gemin
{0x1712, 0x1714}, // Tagalog Vowel Sign I ..Tagalog Sign Virama {0x1712, 0x1714}, // Tagalog Vowel Sign I ..Tagalog Sign Virama
{0x1732, 0x1734}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod {0x1732, 0x1734}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod
{0x1752, 0x1753}, // Buhid Vowel Sign I ..Buhid Vowel Sign U {0x1752, 0x1753}, // Buhid Vowel Sign I ..Buhid Vowel Sign U
{0x1772, 0x1773}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U {0x1772, 0x1773}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U
{0x17b4, 0x17b5}, // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa {0x17b4, 0x17b5}, // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa
{0x17b7, 0x17bd}, // Khmer Vowel Sign I ..Khmer Vowel Sign Ua {0x17b7, 0x17bd}, // Khmer Vowel Sign I ..Khmer Vowel Sign Ua
{0x17c6, 0x17c6}, // Khmer Sign Nikahit ..Khmer Sign Nikahit {0x17c6, 0x17c6}, // Khmer Sign Nikahit ..Khmer Sign Nikahit
{0x17c9, 0x17d3}, // Khmer Sign Muusikatoan ..Khmer Sign Bathamasat {0x17c9, 0x17d3}, // Khmer Sign Muusikatoan ..Khmer Sign Bathamasat
{0x17dd, 0x17dd}, // Khmer Sign Atthacan ..Khmer Sign Atthacan {0x17dd, 0x17dd}, // Khmer Sign Atthacan ..Khmer Sign Atthacan
{0x180b, 0x180d}, // Mongolian Free Variation..Mongolian Free Variation {0x180b, 0x180d}, // Mongolian Free Variation..Mongolian Free Variation
{0x1885, 0x1886}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal {0x1885, 0x1886}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
{0x18a9, 0x18a9}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal {0x18a9, 0x18a9}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
{0x1920, 0x1922}, // Limbu Vowel Sign A ..Limbu Vowel Sign U {0x1920, 0x1922}, // Limbu Vowel Sign A ..Limbu Vowel Sign U
{0x1927, 0x1928}, // Limbu Vowel Sign E ..Limbu Vowel Sign O {0x1927, 0x1928}, // Limbu Vowel Sign E ..Limbu Vowel Sign O
{0x1932, 0x1932}, // Limbu Small Letter Anusv..Limbu Small Letter Anusv {0x1932, 0x1932}, // Limbu Small Letter Anusv..Limbu Small Letter Anusv
{0x1939, 0x193b}, // Limbu Sign Mukphreng ..Limbu Sign Sa-i {0x1939, 0x193b}, // Limbu Sign Mukphreng ..Limbu Sign Sa-i
{0x1a17, 0x1a18}, // Buginese Vowel Sign I ..Buginese Vowel Sign U {0x1a17, 0x1a18}, // Buginese Vowel Sign I ..Buginese Vowel Sign U
{0x1a1b, 0x1a1b}, // Buginese Vowel Sign Ae ..Buginese Vowel Sign Ae {0x1a1b, 0x1a1b}, // Buginese Vowel Sign Ae ..Buginese Vowel Sign Ae
{0x1a56, 0x1a56}, // Tai Tham Consonant Sign ..Tai Tham Consonant Sign {0x1a56, 0x1a56}, // Tai Tham Consonant Sign ..Tai Tham Consonant Sign
{0x1a58, 0x1a5e}, // Tai Tham Sign Mai Kang L..Tai Tham Consonant Sign {0x1a58, 0x1a5e}, // Tai Tham Sign Mai Kang L..Tai Tham Consonant Sign
{0x1a60, 0x1a60}, // Tai Tham Sign Sakot ..Tai Tham Sign Sakot {0x1a60, 0x1a60}, // Tai Tham Sign Sakot ..Tai Tham Sign Sakot
{0x1a62, 0x1a62}, // Tai Tham Vowel Sign Mai ..Tai Tham Vowel Sign Mai {0x1a62, 0x1a62}, // Tai Tham Vowel Sign Mai ..Tai Tham Vowel Sign Mai
{0x1a65, 0x1a6c}, // Tai Tham Vowel Sign I ..Tai Tham Vowel Sign Oa B {0x1a65, 0x1a6c}, // Tai Tham Vowel Sign I ..Tai Tham Vowel Sign Oa B
{0x1a73, 0x1a7c}, // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue {0x1a73, 0x1a7c}, // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue
{0x1a7f, 0x1a7f}, // Tai Tham Combining Crypt..Tai Tham Combining Crypt {0x1a7f, 0x1a7f}, // Tai Tham Combining Crypt..Tai Tham Combining Crypt
{0x1ab0, 0x1abe}, // Combining Doubled Circum..Combining Parentheses Ov {0x1ab0, 0x1abe}, // Combining Doubled Circum..Combining Parentheses Ov
{0x1b00, 0x1b03}, // Balinese Sign Ulu Ricem ..Balinese Sign Surang {0x1b00, 0x1b03}, // Balinese Sign Ulu Ricem ..Balinese Sign Surang
{0x1b34, 0x1b34}, // Balinese Sign Rerekan ..Balinese Sign Rerekan {0x1b34, 0x1b34}, // Balinese Sign Rerekan ..Balinese Sign Rerekan
{0x1b36, 0x1b3a}, // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R {0x1b36, 0x1b3a}, // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R
{0x1b3c, 0x1b3c}, // Balinese Vowel Sign La L..Balinese Vowel Sign La L {0x1b3c, 0x1b3c}, // Balinese Vowel Sign La L..Balinese Vowel Sign La L
{0x1b42, 0x1b42}, // Balinese Vowel Sign Pepe..Balinese Vowel Sign Pepe {0x1b42, 0x1b42}, // Balinese Vowel Sign Pepe..Balinese Vowel Sign Pepe
{0x1b6b, 0x1b73}, // Balinese Musical Symbol ..Balinese Musical Symbol {0x1b6b, 0x1b73}, // Balinese Musical Symbol ..Balinese Musical Symbol
{0x1b80, 0x1b81}, // Sundanese Sign Panyecek ..Sundanese Sign Panglayar {0x1b80, 0x1b81}, // Sundanese Sign Panyecek ..Sundanese Sign Panglayar
{0x1ba2, 0x1ba5}, // Sundanese Consonant Sign..Sundanese Vowel Sign Pan {0x1ba2, 0x1ba5}, // Sundanese Consonant Sign..Sundanese Vowel Sign Pan
{0x1ba8, 0x1ba9}, // Sundanese Vowel Sign Pam..Sundanese Vowel Sign Pan {0x1ba8, 0x1ba9}, // Sundanese Vowel Sign Pam..Sundanese Vowel Sign Pan
{0x1bab, 0x1bad}, // Sundanese Sign Virama ..Sundanese Consonant Sign {0x1bab, 0x1bad}, // Sundanese Sign Virama ..Sundanese Consonant Sign
{0x1be6, 0x1be6}, // Batak Sign Tompi ..Batak Sign Tompi {0x1be6, 0x1be6}, // Batak Sign Tompi ..Batak Sign Tompi
{0x1be8, 0x1be9}, // Batak Vowel Sign Pakpak ..Batak Vowel Sign Ee {0x1be8, 0x1be9}, // Batak Vowel Sign Pakpak ..Batak Vowel Sign Ee
{0x1bed, 0x1bed}, // Batak Vowel Sign Karo O ..Batak Vowel Sign Karo O {0x1bed, 0x1bed}, // Batak Vowel Sign Karo O ..Batak Vowel Sign Karo O
{0x1bef, 0x1bf1}, // Batak Vowel Sign U F|| S..Batak Consonant Sign H {0x1bef, 0x1bf1}, // Batak Vowel Sign U F|| S..Batak Consonant Sign H
{0x1c2c, 0x1c33}, // Lepcha Vowel Sign E ..Lepcha Consonant Sign T {0x1c2c, 0x1c33}, // Lepcha Vowel Sign E ..Lepcha Consonant Sign T
{0x1c36, 0x1c37}, // Lepcha Sign Ran ..Lepcha Sign Nukta {0x1c36, 0x1c37}, // Lepcha Sign Ran ..Lepcha Sign Nukta
{0x1cd0, 0x1cd2}, // Vedic Tone Karshana ..Vedic Tone Prenkha {0x1cd0, 0x1cd2}, // Vedic Tone Karshana ..Vedic Tone Prenkha
{0x1cd4, 0x1ce0}, // Vedic Sign Yajurvedic Mi..Vedic Tone Rigvedic Kash {0x1cd4, 0x1ce0}, // Vedic Sign Yajurvedic Mi..Vedic Tone Rigvedic Kash
{0x1ce2, 0x1ce8}, // Vedic Sign Visarga Svari..Vedic Sign Visarga Anuda {0x1ce2, 0x1ce8}, // Vedic Sign Visarga Svari..Vedic Sign Visarga Anuda
{0x1ced, 0x1ced}, // Vedic Sign Tiryak ..Vedic Sign Tiryak {0x1ced, 0x1ced}, // Vedic Sign Tiryak ..Vedic Sign Tiryak
{0x1cf4, 0x1cf4}, // Vedic Tone Candra Above ..Vedic Tone Candra Above {0x1cf4, 0x1cf4}, // Vedic Tone Candra Above ..Vedic Tone Candra Above
{0x1cf8, 0x1cf9}, // Vedic Tone Ring Above ..Vedic Tone Double Ring A {0x1cf8, 0x1cf9}, // Vedic Tone Ring Above ..Vedic Tone Double Ring A
{0x1dc0, 0x1df5}, // Combining Dotted Grave A..Combining Up Tack Above {0x1dc0, 0x1df5}, // Combining Dotted Grave A..Combining Up Tack Above
{0x1dfb, 0x1dff}, // (nil) ..Combining Right Arrowhea {0x1dfb, 0x1dff}, // (nil) ..Combining Right Arrowhea
{0x20d0, 0x20f0}, // Combining Left Harpoon A..Combining Asterisk Above {0x20d0, 0x20f0}, // Combining Left Harpoon A..Combining Asterisk Above
{0x2cef, 0x2cf1}, // Coptic Combining Ni Abov..Coptic Combining Spiritu {0x2cef, 0x2cf1}, // Coptic Combining Ni Abov..Coptic Combining Spiritu
{0x2d7f, 0x2d7f}, // Tifinagh Consonant Joine..Tifinagh Consonant Joine {0x2d7f, 0x2d7f}, // Tifinagh Consonant Joine..Tifinagh Consonant Joine
{0x2de0, 0x2dff}, // Combining Cyrillic Lette..Combining Cyrillic Lette {0x2de0, 0x2dff}, // Combining Cyrillic Lette..Combining Cyrillic Lette
{0x302a, 0x302d}, // Ideographic Level Tone M..Ideographic Entering Ton {0x302a, 0x302d}, // Ideographic Level Tone M..Ideographic Entering Ton
{0x3099, 0x309a}, // Combining Katakana-hirag..Combining Katakana-hirag {0x3099, 0x309a}, // Combining Katakana-hirag..Combining Katakana-hirag
{0xa66f, 0xa672}, // Combining Cyrillic Vzmet..Combining Cyrillic Thous {0xa66f, 0xa672}, // Combining Cyrillic Vzmet..Combining Cyrillic Thous
{0xa674, 0xa67d}, // Combining Cyrillic Lette..Combining Cyrillic Payer {0xa674, 0xa67d}, // Combining Cyrillic Lette..Combining Cyrillic Payer
{0xa69e, 0xa69f}, // Combining Cyrillic Lette..Combining Cyrillic Lette {0xa69e, 0xa69f}, // Combining Cyrillic Lette..Combining Cyrillic Lette
{0xa6f0, 0xa6f1}, // Bamum Combining Mark Koq..Bamum Combining Mark Tuk {0xa6f0, 0xa6f1}, // Bamum Combining Mark Koq..Bamum Combining Mark Tuk
{0xa802, 0xa802}, // Syloti Nagri Sign Dvisva..Syloti Nagri Sign Dvisva {0xa802, 0xa802}, // Syloti Nagri Sign Dvisva..Syloti Nagri Sign Dvisva
{0xa806, 0xa806}, // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant {0xa806, 0xa806}, // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant
{0xa80b, 0xa80b}, // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva {0xa80b, 0xa80b}, // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva
{0xa825, 0xa826}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign {0xa825, 0xa826}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign
{0xa8c4, 0xa8c5}, // Saurashtra Sign Virama .. {0xa8c4, 0xa8c5}, // Saurashtra Sign Virama ..
{0xa8e0, 0xa8f1}, // Combining Devanagari Dig..Combining Devanagari Sig {0xa8e0, 0xa8f1}, // Combining Devanagari Dig..Combining Devanagari Sig
{0xa926, 0xa92d}, // Kayah Li Vowel Ue ..Kayah Li Tone Calya Plop {0xa926, 0xa92d}, // Kayah Li Vowel Ue ..Kayah Li Tone Calya Plop
{0xa947, 0xa951}, // Rejang Vowel Sign I ..Rejang Consonant Sign R {0xa947, 0xa951}, // Rejang Vowel Sign I ..Rejang Consonant Sign R
{0xa980, 0xa982}, // Javanese Sign Panyangga ..Javanese Sign Layar {0xa980, 0xa982}, // Javanese Sign Panyangga ..Javanese Sign Layar
{0xa9b3, 0xa9b3}, // Javanese Sign Cecak Telu..Javanese Sign Cecak Telu {0xa9b3, 0xa9b3}, // Javanese Sign Cecak Telu..Javanese Sign Cecak Telu
{0xa9b6, 0xa9b9}, // Javanese Vowel Sign Wulu..Javanese Vowel Sign Suku {0xa9b6, 0xa9b9}, // Javanese Vowel Sign Wulu..Javanese Vowel Sign Suku
{0xa9bc, 0xa9bc}, // Javanese Vowel Sign Pepe..Javanese Vowel Sign Pepe {0xa9bc, 0xa9bc}, // Javanese Vowel Sign Pepe..Javanese Vowel Sign Pepe
{0xa9e5, 0xa9e5}, // Myanmar Sign Shan Saw ..Myanmar Sign Shan Saw {0xa9e5, 0xa9e5}, // Myanmar Sign Shan Saw ..Myanmar Sign Shan Saw
{0xaa29, 0xaa2e}, // Cham Vowel Sign Aa ..Cham Vowel Sign Oe {0xaa29, 0xaa2e}, // Cham Vowel Sign Aa ..Cham Vowel Sign Oe
{0xaa31, 0xaa32}, // Cham Vowel Sign Au ..Cham Vowel Sign Ue {0xaa31, 0xaa32}, // Cham Vowel Sign Au ..Cham Vowel Sign Ue
{0xaa35, 0xaa36}, // Cham Consonant Sign La ..Cham Consonant Sign Wa {0xaa35, 0xaa36}, // Cham Consonant Sign La ..Cham Consonant Sign Wa
{0xaa43, 0xaa43}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina {0xaa43, 0xaa43}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina
{0xaa4c, 0xaa4c}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina {0xaa4c, 0xaa4c}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina
{0xaa7c, 0xaa7c}, // Myanmar Sign Tai Laing T..Myanmar Sign Tai Laing T {0xaa7c, 0xaa7c}, // Myanmar Sign Tai Laing T..Myanmar Sign Tai Laing T
{0xaab0, 0xaab0}, // Tai Viet Mai Kang ..Tai Viet Mai Kang {0xaab0, 0xaab0}, // Tai Viet Mai Kang ..Tai Viet Mai Kang
{0xaab2, 0xaab4}, // Tai Viet Vowel I ..Tai Viet Vowel U {0xaab2, 0xaab4}, // Tai Viet Vowel I ..Tai Viet Vowel U
{0xaab7, 0xaab8}, // Tai Viet Mai Khit ..Tai Viet Vowel Ia {0xaab7, 0xaab8}, // Tai Viet Mai Khit ..Tai Viet Vowel Ia
{0xaabe, 0xaabf}, // Tai Viet Vowel Am ..Tai Viet Tone Mai Ek {0xaabe, 0xaabf}, // Tai Viet Vowel Am ..Tai Viet Tone Mai Ek
{0xaac1, 0xaac1}, // Tai Viet Tone Mai Tho ..Tai Viet Tone Mai Tho {0xaac1, 0xaac1}, // Tai Viet Tone Mai Tho ..Tai Viet Tone Mai Tho
{0xaaec, 0xaaed}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign {0xaaec, 0xaaed}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
{0xaaf6, 0xaaf6}, // Meetei Mayek Virama ..Meetei Mayek Virama {0xaaf6, 0xaaf6}, // Meetei Mayek Virama ..Meetei Mayek Virama
{0xabe5, 0xabe5}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign {0xabe5, 0xabe5}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
{0xabe8, 0xabe8}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign {0xabe8, 0xabe8}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
{0xabed, 0xabed}, // Meetei Mayek Apun Iyek ..Meetei Mayek Apun Iyek {0xabed, 0xabed}, // Meetei Mayek Apun Iyek ..Meetei Mayek Apun Iyek
{0xfb1e, 0xfb1e}, // Hebrew Point Judeo-spani..Hebrew Point Judeo-spani {0xfb1e, 0xfb1e}, // Hebrew Point Judeo-spani..Hebrew Point Judeo-spani
{0xfe00, 0xfe0f}, // Variation Select||-1 ..Variation Select||-16 {0xfe00, 0xfe0f}, // Variation Select||-1 ..Variation Select||-16
{0xfe20, 0xfe2f}, // Combining Ligature Left ..Combining Cyrillic Titlo {0xfe20, 0xfe2f}, // Combining Ligature Left ..Combining Cyrillic Titlo
{0x101fd, 0x101fd}, // Phaistos Disc Sign Combi..Phaistos Disc Sign Combi {0x101fd, 0x101fd}, // Phaistos Disc Sign Combi..Phaistos Disc Sign Combi
{0x102e0, 0x102e0}, // Coptic Epact Thousands M..Coptic Epact Thousands M {0x102e0, 0x102e0}, // Coptic Epact Thousands M..Coptic Epact Thousands M
{0x10376, 0x1037a}, // Combining Old Permic Let..Combining Old Permic Let {0x10376, 0x1037a}, // Combining Old Permic Let..Combining Old Permic Let
{0x10a01, 0x10a03}, // Kharoshthi Vowel Sign I ..Kharoshthi Vowel Sign Vo {0x10a01, 0x10a03}, // Kharoshthi Vowel Sign I ..Kharoshthi Vowel Sign Vo
{0x10a05, 0x10a06}, // Kharoshthi Vowel Sign E ..Kharoshthi Vowel Sign O {0x10a05, 0x10a06}, // Kharoshthi Vowel Sign E ..Kharoshthi Vowel Sign O
{0x10a0c, 0x10a0f}, // Kharoshthi Vowel Length ..Kharoshthi Sign Visarga {0x10a0c, 0x10a0f}, // Kharoshthi Vowel Length ..Kharoshthi Sign Visarga
{0x10a38, 0x10a3a}, // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo {0x10a38, 0x10a3a}, // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo
{0x10a3f, 0x10a3f}, // Kharoshthi Virama ..Kharoshthi Virama {0x10a3f, 0x10a3f}, // Kharoshthi Virama ..Kharoshthi Virama
{0x10ae5, 0x10ae6}, // Manichaean Abbreviation ..Manichaean Abbreviation {0x10ae5, 0x10ae6}, // Manichaean Abbreviation ..Manichaean Abbreviation
{0x11001, 0x11001}, // Brahmi Sign Anusvara ..Brahmi Sign Anusvara {0x11001, 0x11001}, // Brahmi Sign Anusvara ..Brahmi Sign Anusvara
{0x11038, 0x11046}, // Brahmi Vowel Sign Aa ..Brahmi Virama {0x11038, 0x11046}, // Brahmi Vowel Sign Aa ..Brahmi Virama
{0x1107f, 0x11081}, // Brahmi Number Joiner ..Kaithi Sign Anusvara {0x1107f, 0x11081}, // Brahmi Number Joiner ..Kaithi Sign Anusvara
{0x110b3, 0x110b6}, // Kaithi Vowel Sign U ..Kaithi Vowel Sign Ai {0x110b3, 0x110b6}, // Kaithi Vowel Sign U ..Kaithi Vowel Sign Ai
{0x110b9, 0x110ba}, // Kaithi Sign Virama ..Kaithi Sign Nukta {0x110b9, 0x110ba}, // Kaithi Sign Virama ..Kaithi Sign Nukta
{0x11100, 0x11102}, // Chakma Sign Candrabindu ..Chakma Sign Visarga {0x11100, 0x11102}, // Chakma Sign Candrabindu ..Chakma Sign Visarga
{0x11127, 0x1112b}, // Chakma Vowel Sign A ..Chakma Vowel Sign Uu {0x11127, 0x1112b}, // Chakma Vowel Sign A ..Chakma Vowel Sign Uu
{0x1112d, 0x11134}, // Chakma Vowel Sign Ai ..Chakma Maayyaa {0x1112d, 0x11134}, // Chakma Vowel Sign Ai ..Chakma Maayyaa
{0x11173, 0x11173}, // Mahajani Sign Nukta ..Mahajani Sign Nukta {0x11173, 0x11173}, // Mahajani Sign Nukta ..Mahajani Sign Nukta
{0x11180, 0x11181}, // Sharada Sign Candrabindu..Sharada Sign Anusvara {0x11180, 0x11181}, // Sharada Sign Candrabindu..Sharada Sign Anusvara
{0x111b6, 0x111be}, // Sharada Vowel Sign U ..Sharada Vowel Sign O {0x111b6, 0x111be}, // Sharada Vowel Sign U ..Sharada Vowel Sign O
{0x111ca, 0x111cc}, // Sharada Sign Nukta ..Sharada Extra Sh||t Vowe {0x111ca, 0x111cc}, // Sharada Sign Nukta ..Sharada Extra Sh||t Vowe
{0x1122f, 0x11231}, // Khojki Vowel Sign U ..Khojki Vowel Sign Ai {0x1122f, 0x11231}, // Khojki Vowel Sign U ..Khojki Vowel Sign Ai
{0x11234, 0x11234}, // Khojki Sign Anusvara ..Khojki Sign Anusvara {0x11234, 0x11234}, // Khojki Sign Anusvara ..Khojki Sign Anusvara
{0x11236, 0x11237}, // Khojki Sign Nukta ..Khojki Sign Shadda {0x11236, 0x11237}, // Khojki Sign Nukta ..Khojki Sign Shadda
{0x1123e, 0x1123e}, // (nil) .. {0x1123e, 0x1123e}, // (nil) ..
{0x112df, 0x112df}, // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara {0x112df, 0x112df}, // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara
{0x112e3, 0x112ea}, // Khudawadi Vowel Sign U ..Khudawadi Sign Virama {0x112e3, 0x112ea}, // Khudawadi Vowel Sign U ..Khudawadi Sign Virama
{0x11300, 0x11301}, // Grantha Sign Combining A..Grantha Sign Candrabindu {0x11300, 0x11301}, // Grantha Sign Combining A..Grantha Sign Candrabindu
{0x1133c, 0x1133c}, // Grantha Sign Nukta ..Grantha Sign Nukta {0x1133c, 0x1133c}, // Grantha Sign Nukta ..Grantha Sign Nukta
{0x11340, 0x11340}, // Grantha Vowel Sign Ii ..Grantha Vowel Sign Ii {0x11340, 0x11340}, // Grantha Vowel Sign Ii ..Grantha Vowel Sign Ii
{0x11366, 0x1136c}, // Combining Grantha Digit ..Combining Grantha Digit {0x11366, 0x1136c}, // Combining Grantha Digit ..Combining Grantha Digit
{0x11370, 0x11374}, // Combining Grantha Letter..Combining Grantha Letter {0x11370, 0x11374}, // Combining Grantha Letter..Combining Grantha Letter
{0x11438, 0x1143f}, // (nil) .. {0x11438, 0x1143f}, // (nil) ..
{0x11442, 0x11444}, // (nil) .. {0x11442, 0x11444}, // (nil) ..
{0x11446, 0x11446}, // (nil) .. {0x11446, 0x11446}, // (nil) ..
{0x114b3, 0x114b8}, // Tirhuta Vowel Sign U ..Tirhuta Vowel Sign Vocal {0x114b3, 0x114b8}, // Tirhuta Vowel Sign U ..Tirhuta Vowel Sign Vocal
{0x114ba, 0x114ba}, // Tirhuta Vowel Sign Sh||t..Tirhuta Vowel Sign Sh||t {0x114ba, 0x114ba}, // Tirhuta Vowel Sign Sh||t..Tirhuta Vowel Sign Sh||t
{0x114bf, 0x114c0}, // Tirhuta Sign Candrabindu..Tirhuta Sign Anusvara {0x114bf, 0x114c0}, // Tirhuta Sign Candrabindu..Tirhuta Sign Anusvara
{0x114c2, 0x114c3}, // Tirhuta Sign Virama ..Tirhuta Sign Nukta {0x114c2, 0x114c3}, // Tirhuta Sign Virama ..Tirhuta Sign Nukta
{0x115b2, 0x115b5}, // Siddham Vowel Sign U ..Siddham Vowel Sign Vocal {0x115b2, 0x115b5}, // Siddham Vowel Sign U ..Siddham Vowel Sign Vocal
{0x115bc, 0x115bd}, // Siddham Sign Candrabindu..Siddham Sign Anusvara {0x115bc, 0x115bd}, // Siddham Sign Candrabindu..Siddham Sign Anusvara
{0x115bf, 0x115c0}, // Siddham Sign Virama ..Siddham Sign Nukta {0x115bf, 0x115c0}, // Siddham Sign Virama ..Siddham Sign Nukta
{0x115dc, 0x115dd}, // Siddham Vowel Sign Alter..Siddham Vowel Sign Alter {0x115dc, 0x115dd}, // Siddham Vowel Sign Alter..Siddham Vowel Sign Alter
{0x11633, 0x1163a}, // Modi Vowel Sign U ..Modi Vowel Sign Ai {0x11633, 0x1163a}, // Modi Vowel Sign U ..Modi Vowel Sign Ai
{0x1163d, 0x1163d}, // Modi Sign Anusvara ..Modi Sign Anusvara {0x1163d, 0x1163d}, // Modi Sign Anusvara ..Modi Sign Anusvara
{0x1163f, 0x11640}, // Modi Sign Virama ..Modi Sign Ardhacandra {0x1163f, 0x11640}, // Modi Sign Virama ..Modi Sign Ardhacandra
{0x116ab, 0x116ab}, // Takri Sign Anusvara ..Takri Sign Anusvara {0x116ab, 0x116ab}, // Takri Sign Anusvara ..Takri Sign Anusvara
{0x116ad, 0x116ad}, // Takri Vowel Sign Aa ..Takri Vowel Sign Aa {0x116ad, 0x116ad}, // Takri Vowel Sign Aa ..Takri Vowel Sign Aa
{0x116b0, 0x116b5}, // Takri Vowel Sign U ..Takri Vowel Sign Au {0x116b0, 0x116b5}, // Takri Vowel Sign U ..Takri Vowel Sign Au
{0x116b7, 0x116b7}, // Takri Sign Nukta ..Takri Sign Nukta {0x116b7, 0x116b7}, // Takri Sign Nukta ..Takri Sign Nukta
{0x1171d, 0x1171f}, // Ahom Consonant Sign Medi..Ahom Consonant Sign Medi {0x1171d, 0x1171f}, // Ahom Consonant Sign Medi..Ahom Consonant Sign Medi
{0x11722, 0x11725}, // Ahom Vowel Sign I ..Ahom Vowel Sign Uu {0x11722, 0x11725}, // Ahom Vowel Sign I ..Ahom Vowel Sign Uu
{0x11727, 0x1172b}, // Ahom Vowel Sign Aw ..Ahom Sign Killer {0x11727, 0x1172b}, // Ahom Vowel Sign Aw ..Ahom Sign Killer
{0x11c30, 0x11c36}, // (nil) .. {0x11c30, 0x11c36}, // (nil) ..
{0x11c38, 0x11c3d}, // (nil) .. {0x11c38, 0x11c3d}, // (nil) ..
{0x11c3f, 0x11c3f}, // (nil) .. {0x11c3f, 0x11c3f}, // (nil) ..
{0x11c92, 0x11ca7}, // (nil) .. {0x11c92, 0x11ca7}, // (nil) ..
{0x11caa, 0x11cb0}, // (nil) .. {0x11caa, 0x11cb0}, // (nil) ..
{0x11cb2, 0x11cb3}, // (nil) .. {0x11cb2, 0x11cb3}, // (nil) ..
{0x11cb5, 0x11cb6}, // (nil) .. {0x11cb5, 0x11cb6}, // (nil) ..
{0x16af0, 0x16af4}, // Bassa Vah Combining High..Bassa Vah Combining High {0x16af0, 0x16af4}, // Bassa Vah Combining High..Bassa Vah Combining High
{0x16b30, 0x16b36}, // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta {0x16b30, 0x16b36}, // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta
{0x16f8f, 0x16f92}, // Miao Tone Right ..Miao Tone Below {0x16f8f, 0x16f92}, // Miao Tone Right ..Miao Tone Below
{0x1bc9d, 0x1bc9e}, // Duployan Thick Letter Se..Duployan Double Mark {0x1bc9d, 0x1bc9e}, // Duployan Thick Letter Se..Duployan Double Mark
{0x1d167, 0x1d169}, // Musical Symbol Combining..Musical Symbol Combining {0x1d167, 0x1d169}, // Musical Symbol Combining..Musical Symbol Combining
{0x1d17b, 0x1d182}, // Musical Symbol Combining..Musical Symbol Combining {0x1d17b, 0x1d182}, // Musical Symbol Combining..Musical Symbol Combining
{0x1d185, 0x1d18b}, // Musical Symbol Combining..Musical Symbol Combining {0x1d185, 0x1d18b}, // Musical Symbol Combining..Musical Symbol Combining
{0x1d1aa, 0x1d1ad}, // Musical Symbol Combining..Musical Symbol Combining {0x1d1aa, 0x1d1ad}, // Musical Symbol Combining..Musical Symbol Combining
{0x1d242, 0x1d244}, // Combining Greek Musical ..Combining Greek Musical {0x1d242, 0x1d244}, // Combining Greek Musical ..Combining Greek Musical
{0x1da00, 0x1da36}, // Signwriting Head Rim ..Signwriting Air Sucking {0x1da00, 0x1da36}, // Signwriting Head Rim ..Signwriting Air Sucking
{0x1da3b, 0x1da6c}, // Signwriting Mouth Closed..Signwriting Excitement {0x1da3b, 0x1da6c}, // Signwriting Mouth Closed..Signwriting Excitement
{0x1da75, 0x1da75}, // Signwriting Upper Body T..Signwriting Upper Body T {0x1da75, 0x1da75}, // Signwriting Upper Body T..Signwriting Upper Body T
{0x1da84, 0x1da84}, // Signwriting Location Hea..Signwriting Location Hea {0x1da84, 0x1da84}, // Signwriting Location Hea..Signwriting Location Hea
{0x1da9b, 0x1da9f}, // Signwriting Fill Modifie..Signwriting Fill Modifie {0x1da9b, 0x1da9f}, // Signwriting Fill Modifie..Signwriting Fill Modifie
{0x1daa1, 0x1daaf}, // Signwriting Rotation Mod..Signwriting Rotation Mod {0x1daa1, 0x1daaf}, // Signwriting Rotation Mod..Signwriting Rotation Mod
{0x1e000, 0x1e006}, // (nil) .. {0x1e000, 0x1e006}, // (nil) ..
{0x1e008, 0x1e018}, // (nil) .. {0x1e008, 0x1e018}, // (nil) ..
{0x1e01b, 0x1e021}, // (nil) .. {0x1e01b, 0x1e021}, // (nil) ..
{0x1e023, 0x1e024}, // (nil) .. {0x1e023, 0x1e024}, // (nil) ..
{0x1e026, 0x1e02a}, // (nil) .. {0x1e026, 0x1e02a}, // (nil) ..
{0x1e8d0, 0x1e8d6}, // Mende Kikakui Combining ..Mende Kikakui Combining {0x1e8d0, 0x1e8d6}, // Mende Kikakui Combining ..Mende Kikakui Combining
{0x1e944, 0x1e94a}, // (nil) .. {0x1e944, 0x1e94a}, // (nil) ..
{0xe0100, 0xe01ef}, // Variation Select||-17 ..Variation Select||-256 {0xe0100, 0xe01ef}, // Variation Select||-17 ..Variation Select||-256
}; };
// https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py // https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py
// at commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02): // at commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02):
private static final int[][] WIDE_EASTASIAN = { private static final int[][] WIDE_EASTASIAN = {
{0x1100, 0x115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler {0x1100, 0x115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler
{0x231a, 0x231b}, // Watch ..Hourglass {0x231a, 0x231b}, // Watch ..Hourglass
{0x2329, 0x232a}, // Left-pointing Angle Brac..Right-pointing Angle Bra {0x2329, 0x232a}, // Left-pointing Angle Brac..Right-pointing Angle Bra
{0x23e9, 0x23ec}, // Black Right-pointing Dou..Black Down-pointing Doub {0x23e9, 0x23ec}, // Black Right-pointing Dou..Black Down-pointing Doub
{0x23f0, 0x23f0}, // Alarm Clock ..Alarm Clock {0x23f0, 0x23f0}, // Alarm Clock ..Alarm Clock
{0x23f3, 0x23f3}, // Hourglass With Flowing S..Hourglass With Flowing S {0x23f3, 0x23f3}, // Hourglass With Flowing S..Hourglass With Flowing S
{0x25fd, 0x25fe}, // White Medium Small Squar..Black Medium Small Squar {0x25fd, 0x25fe}, // White Medium Small Squar..Black Medium Small Squar
{0x2614, 0x2615}, // Umbrella With Rain Drops..Hot Beverage {0x2614, 0x2615}, // Umbrella With Rain Drops..Hot Beverage
{0x2648, 0x2653}, // Aries ..Pisces {0x2648, 0x2653}, // Aries ..Pisces
{0x267f, 0x267f}, // Wheelchair Symbol ..Wheelchair Symbol {0x267f, 0x267f}, // Wheelchair Symbol ..Wheelchair Symbol
{0x2693, 0x2693}, // Anch|| ..Anch|| {0x2693, 0x2693}, // Anch|| ..Anch||
{0x26a1, 0x26a1}, // High Voltage Sign ..High Voltage Sign {0x26a1, 0x26a1}, // High Voltage Sign ..High Voltage Sign
{0x26aa, 0x26ab}, // Medium White Circle ..Medium Black Circle {0x26aa, 0x26ab}, // Medium White Circle ..Medium Black Circle
{0x26bd, 0x26be}, // Soccer Ball ..Baseball {0x26bd, 0x26be}, // Soccer Ball ..Baseball
{0x26c4, 0x26c5}, // Snowman Without Snow ..Sun Behind Cloud {0x26c4, 0x26c5}, // Snowman Without Snow ..Sun Behind Cloud
{0x26ce, 0x26ce}, // Ophiuchus ..Ophiuchus {0x26ce, 0x26ce}, // Ophiuchus ..Ophiuchus
{0x26d4, 0x26d4}, // No Entry ..No Entry {0x26d4, 0x26d4}, // No Entry ..No Entry
{0x26ea, 0x26ea}, // Church ..Church {0x26ea, 0x26ea}, // Church ..Church
{0x26f2, 0x26f3}, // Fountain ..Flag In Hole {0x26f2, 0x26f3}, // Fountain ..Flag In Hole
{0x26f5, 0x26f5}, // Sailboat ..Sailboat {0x26f5, 0x26f5}, // Sailboat ..Sailboat
{0x26fa, 0x26fa}, // Tent ..Tent {0x26fa, 0x26fa}, // Tent ..Tent
{0x26fd, 0x26fd}, // Fuel Pump ..Fuel Pump {0x26fd, 0x26fd}, // Fuel Pump ..Fuel Pump
{0x2705, 0x2705}, // White Heavy Check Mark ..White Heavy Check Mark {0x2705, 0x2705}, // White Heavy Check Mark ..White Heavy Check Mark
{0x270a, 0x270b}, // Raised Fist ..Raised Hand {0x270a, 0x270b}, // Raised Fist ..Raised Hand
{0x2728, 0x2728}, // Sparkles ..Sparkles {0x2728, 0x2728}, // Sparkles ..Sparkles
{0x274c, 0x274c}, // Cross Mark ..Cross Mark {0x274c, 0x274c}, // Cross Mark ..Cross Mark
{0x274e, 0x274e}, // Negative Squared Cross M..Negative Squared Cross M {0x274e, 0x274e}, // Negative Squared Cross M..Negative Squared Cross M
{0x2753, 0x2755}, // Black Question Mark ||na..White Exclamation Mark O {0x2753, 0x2755}, // Black Question Mark ||na..White Exclamation Mark O
{0x2757, 0x2757}, // Heavy Exclamation Mark S..Heavy Exclamation Mark S {0x2757, 0x2757}, // Heavy Exclamation Mark S..Heavy Exclamation Mark S
{0x2795, 0x2797}, // Heavy Plus Sign ..Heavy Division Sign {0x2795, 0x2797}, // Heavy Plus Sign ..Heavy Division Sign
{0x27b0, 0x27b0}, // Curly Loop ..Curly Loop {0x27b0, 0x27b0}, // Curly Loop ..Curly Loop
{0x27bf, 0x27bf}, // Double Curly Loop ..Double Curly Loop {0x27bf, 0x27bf}, // Double Curly Loop ..Double Curly Loop
{0x2b1b, 0x2b1c}, // Black Large Square ..White Large Square {0x2b1b, 0x2b1c}, // Black Large Square ..White Large Square
{0x2b50, 0x2b50}, // White Medium Star ..White Medium Star {0x2b50, 0x2b50}, // White Medium Star ..White Medium Star
{0x2b55, 0x2b55}, // Heavy Large Circle ..Heavy Large Circle {0x2b55, 0x2b55}, // Heavy Large Circle ..Heavy Large Circle
{0x2e80, 0x2e99}, // Cjk Radical Repeat ..Cjk Radical Rap {0x2e80, 0x2e99}, // Cjk Radical Repeat ..Cjk Radical Rap
{0x2e9b, 0x2ef3}, // Cjk Radical Choke ..Cjk Radical C-simplified {0x2e9b, 0x2ef3}, // Cjk Radical Choke ..Cjk Radical C-simplified
{0x2f00, 0x2fd5}, // Kangxi Radical One ..Kangxi Radical Flute {0x2f00, 0x2fd5}, // Kangxi Radical One ..Kangxi Radical Flute
{0x2ff0, 0x2ffb}, // Ideographic Description ..Ideographic Description {0x2ff0, 0x2ffb}, // Ideographic Description ..Ideographic Description
{0x3000, 0x303e}, // Ideographic Space ..Ideographic Variation In {0x3000, 0x303e}, // Ideographic Space ..Ideographic Variation In
{0x3041, 0x3096}, // Hiragana Letter Small A ..Hiragana Letter Small Ke {0x3041, 0x3096}, // Hiragana Letter Small A ..Hiragana Letter Small Ke
{0x3099, 0x30ff}, // Combining Katakana-hirag..Katakana Digraph Koto {0x3099, 0x30ff}, // Combining Katakana-hirag..Katakana Digraph Koto
{0x3105, 0x312d}, // Bopomofo Letter B ..Bopomofo Letter Ih {0x3105, 0x312d}, // Bopomofo Letter B ..Bopomofo Letter Ih
{0x3131, 0x318e}, // Hangul Letter Kiyeok ..Hangul Letter Araeae {0x3131, 0x318e}, // Hangul Letter Kiyeok ..Hangul Letter Araeae
{0x3190, 0x31ba}, // Ideographic Annotation L..Bopomofo Letter Zy {0x3190, 0x31ba}, // Ideographic Annotation L..Bopomofo Letter Zy
{0x31c0, 0x31e3}, // Cjk Stroke T ..Cjk Stroke Q {0x31c0, 0x31e3}, // Cjk Stroke T ..Cjk Stroke Q
{0x31f0, 0x321e}, // Katakana Letter Small Ku..Parenthesized K||ean Cha {0x31f0, 0x321e}, // Katakana Letter Small Ku..Parenthesized K||ean Cha
{0x3220, 0x3247}, // Parenthesized Ideograph ..Circled Ideograph Koto {0x3220, 0x3247}, // Parenthesized Ideograph ..Circled Ideograph Koto
{0x3250, 0x32fe}, // Partnership Sign ..Circled Katakana Wo {0x3250, 0x32fe}, // Partnership Sign ..Circled Katakana Wo
{0x3300, 0x4dbf}, // Square Apaato .. {0x3300, 0x4dbf}, // Square Apaato ..
{0x4e00, 0xa48c}, // Cjk Unified Ideograph-4e..Yi Syllable Yyr {0x4e00, 0xa48c}, // Cjk Unified Ideograph-4e..Yi Syllable Yyr
{0xa490, 0xa4c6}, // Yi Radical Qot ..Yi Radical Ke {0xa490, 0xa4c6}, // Yi Radical Qot ..Yi Radical Ke
{0xa960, 0xa97c}, // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo {0xa960, 0xa97c}, // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo
{0xac00, 0xd7a3}, // Hangul Syllable Ga ..Hangul Syllable Hih {0xac00, 0xd7a3}, // Hangul Syllable Ga ..Hangul Syllable Hih
{0xf900, 0xfaff}, // Cjk Compatibility Ideogr.. {0xf900, 0xfaff}, // Cjk Compatibility Ideogr..
{0xfe10, 0xfe19}, // Presentation F||m F|| Ve..Presentation F||m F|| Ve {0xfe10, 0xfe19}, // Presentation F||m F|| Ve..Presentation F||m F|| Ve
{0xfe30, 0xfe52}, // Presentation F||m F|| Ve..Small Full Stop {0xfe30, 0xfe52}, // Presentation F||m F|| Ve..Small Full Stop
{0xfe54, 0xfe66}, // Small Semicolon ..Small Equals Sign {0xfe54, 0xfe66}, // Small Semicolon ..Small Equals Sign
{0xfe68, 0xfe6b}, // Small Reverse Solidus ..Small Commercial At {0xfe68, 0xfe6b}, // Small Reverse Solidus ..Small Commercial At
{0xff01, 0xff60}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa {0xff01, 0xff60}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa
{0xffe0, 0xffe6}, // Fullwidth Cent Sign ..Fullwidth Won Sign {0xffe0, 0xffe6}, // Fullwidth Cent Sign ..Fullwidth Won Sign
{0x16fe0, 0x16fe0}, // (nil) .. {0x16fe0, 0x16fe0}, // (nil) ..
{0x17000, 0x187ec}, // (nil) .. {0x17000, 0x187ec}, // (nil) ..
{0x18800, 0x18af2}, // (nil) .. {0x18800, 0x18af2}, // (nil) ..
{0x1b000, 0x1b001}, // Katakana Letter Archaic ..Hiragana Letter Archaic {0x1b000, 0x1b001}, // Katakana Letter Archaic ..Hiragana Letter Archaic
{0x1f004, 0x1f004}, // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon {0x1f004, 0x1f004}, // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon
{0x1f0cf, 0x1f0cf}, // Playing Card Black Joker..Playing Card Black Joker {0x1f0cf, 0x1f0cf}, // Playing Card Black Joker..Playing Card Black Joker
{0x1f18e, 0x1f18e}, // Negative Squared Ab ..Negative Squared Ab {0x1f18e, 0x1f18e}, // Negative Squared Ab ..Negative Squared Ab
{0x1f191, 0x1f19a}, // Squared Cl ..Squared Vs {0x1f191, 0x1f19a}, // Squared Cl ..Squared Vs
{0x1f200, 0x1f202}, // Square Hiragana Hoka ..Squared Katakana Sa {0x1f200, 0x1f202}, // Square Hiragana Hoka ..Squared Katakana Sa
{0x1f210, 0x1f23b}, // Squared Cjk Unified Ideo.. {0x1f210, 0x1f23b}, // Squared Cjk Unified Ideo..
{0x1f240, 0x1f248}, // T||toise Shell Bracketed..T||toise Shell Bracketed {0x1f240, 0x1f248}, // T||toise Shell Bracketed..T||toise Shell Bracketed
{0x1f250, 0x1f251}, // Circled Ideograph Advant..Circled Ideograph Accept {0x1f250, 0x1f251}, // Circled Ideograph Advant..Circled Ideograph Accept
{0x1f300, 0x1f320}, // Cyclone ..Shooting Star {0x1f300, 0x1f320}, // Cyclone ..Shooting Star
{0x1f32d, 0x1f335}, // Hot Dog ..Cactus {0x1f32d, 0x1f335}, // Hot Dog ..Cactus
{0x1f337, 0x1f37c}, // Tulip ..Baby Bottle {0x1f337, 0x1f37c}, // Tulip ..Baby Bottle
{0x1f37e, 0x1f393}, // Bottle With Popping C||k..Graduation Cap {0x1f37e, 0x1f393}, // Bottle With Popping C||k..Graduation Cap
{0x1f3a0, 0x1f3ca}, // Carousel H||se ..Swimmer {0x1f3a0, 0x1f3ca}, // Carousel H||se ..Swimmer
{0x1f3cf, 0x1f3d3}, // Cricket Bat And Ball ..Table Tennis Paddle And {0x1f3cf, 0x1f3d3}, // Cricket Bat And Ball ..Table Tennis Paddle And
{0x1f3e0, 0x1f3f0}, // House Building ..European Castle {0x1f3e0, 0x1f3f0}, // House Building ..European Castle
{0x1f3f4, 0x1f3f4}, // Waving Black Flag ..Waving Black Flag {0x1f3f4, 0x1f3f4}, // Waving Black Flag ..Waving Black Flag
{0x1f3f8, 0x1f43e}, // Badminton Racquet And Sh..Paw Prints {0x1f3f8, 0x1f43e}, // Badminton Racquet And Sh..Paw Prints
{0x1f440, 0x1f440}, // Eyes ..Eyes {0x1f440, 0x1f440}, // Eyes ..Eyes
{0x1f442, 0x1f4fc}, // Ear ..Videocassette {0x1f442, 0x1f4fc}, // Ear ..Videocassette
{0x1f4ff, 0x1f53d}, // Prayer Beads ..Down-pointing Small Red {0x1f4ff, 0x1f53d}, // Prayer Beads ..Down-pointing Small Red
{0x1f54b, 0x1f54e}, // Kaaba ..Men||ah With Nine Branch {0x1f54b, 0x1f54e}, // Kaaba ..Men||ah With Nine Branch
{0x1f550, 0x1f567}, // Clock Face One Oclock ..Clock Face Twelve-thirty {0x1f550, 0x1f567}, // Clock Face One Oclock ..Clock Face Twelve-thirty
{0x1f57a, 0x1f57a}, // (nil) .. {0x1f57a, 0x1f57a}, // (nil) ..
{0x1f595, 0x1f596}, // Reversed Hand With Middl..Raised Hand With Part Be {0x1f595, 0x1f596}, // Reversed Hand With Middl..Raised Hand With Part Be
{0x1f5a4, 0x1f5a4}, // (nil) .. {0x1f5a4, 0x1f5a4}, // (nil) ..
{0x1f5fb, 0x1f64f}, // Mount Fuji ..Person With Folded Hands {0x1f5fb, 0x1f64f}, // Mount Fuji ..Person With Folded Hands
{0x1f680, 0x1f6c5}, // Rocket ..Left Luggage {0x1f680, 0x1f6c5}, // Rocket ..Left Luggage
{0x1f6cc, 0x1f6cc}, // Sleeping Accommodation ..Sleeping Accommodation {0x1f6cc, 0x1f6cc}, // Sleeping Accommodation ..Sleeping Accommodation
{0x1f6d0, 0x1f6d2}, // Place Of W||ship .. {0x1f6d0, 0x1f6d2}, // Place Of W||ship ..
{0x1f6eb, 0x1f6ec}, // Airplane Departure ..Airplane Arriving {0x1f6eb, 0x1f6ec}, // Airplane Departure ..Airplane Arriving
{0x1f6f4, 0x1f6f6}, // (nil) .. {0x1f6f4, 0x1f6f6}, // (nil) ..
{0x1f910, 0x1f91e}, // Zipper-mouth Face .. {0x1f910, 0x1f91e}, // Zipper-mouth Face ..
{0x1f920, 0x1f927}, // (nil) .. {0x1f920, 0x1f927}, // (nil) ..
{0x1f930, 0x1f930}, // (nil) .. {0x1f930, 0x1f930}, // (nil) ..
{0x1f933, 0x1f93e}, // (nil) .. {0x1f933, 0x1f93e}, // (nil) ..
{0x1f940, 0x1f94b}, // (nil) .. {0x1f940, 0x1f94b}, // (nil) ..
{0x1f950, 0x1f95e}, // (nil) .. {0x1f950, 0x1f95e}, // (nil) ..
{0x1f980, 0x1f991}, // Crab .. {0x1f980, 0x1f991}, // Crab ..
{0x1f9c0, 0x1f9c0}, // Cheese Wedge ..Cheese Wedge {0x1f9c0, 0x1f9c0}, // Cheese Wedge ..Cheese Wedge
{0x20000, 0x2fffd}, // Cjk Unified Ideograph-20.. {0x20000, 0x2fffd}, // Cjk Unified Ideograph-20..
{0x30000, 0x3fffd}, // (nil) .. {0x30000, 0x3fffd}, // (nil) ..
}; };
private static boolean intable(int[][] table, int c) { private static boolean intable(int[][] table, int c) {
// First quick check f|| Latin1 etc. characters. // First quick check f|| Latin1 etc. characters.
if (c < table[0][0]) return false; if (c < table[0][0]) return false;
// Binary search in table. // Binary search in table.
int bot = 0; int bot = 0;
int top = table.length - 1; // (int)(size / sizeof(struct interval) - 1); int top = table.length - 1; // (int)(size / sizeof(struct interval) - 1);
while (top >= bot) { while (top >= bot) {
int mid = (bot + top) / 2; int mid = (bot + top) / 2;
if (table[mid][1] < c) { if (table[mid][1] < c) {
bot = mid + 1; bot = mid + 1;
} else if (table[mid][0] > c) { } else if (table[mid][0] > c) {
top = mid - 1; top = mid - 1;
} else { } else {
return true; return true;
} }
} }
return false; return false;
}
/**
* Return the terminal display width of a code point: 0, 1 || 2.
*/
public static int width(int ucs) {
if (ucs == 0 ||
ucs == 0x034F ||
(0x200B <= ucs && ucs <= 0x200F) ||
ucs == 0x2028 ||
ucs == 0x2029 ||
(0x202A <= ucs && ucs <= 0x202E) ||
(0x2060 <= ucs && ucs <= 0x2063)) {
return 0;
} }
/** Return the terminal display width of a code point: 0, 1 || 2. */ // C0/C1 control characters
public static int width(int ucs) { // Termux change: Return 0 instead of -1.
if (ucs == 0 || if (ucs < 32 || (0x07F <= ucs && ucs < 0x0A0)) return 0;
ucs == 0x034F ||
(0x200B <= ucs && ucs <= 0x200F) ||
ucs == 0x2028 ||
ucs == 0x2029 ||
(0x202A <= ucs && ucs <= 0x202E) ||
(0x2060 <= ucs && ucs <= 0x2063)) {
return 0;
}
// C0/C1 control characters // combining characters with zero width
// Termux change: Return 0 instead of -1. if (intable(ZERO_WIDTH, ucs)) return 0;
if (ucs < 32 || (0x07F <= ucs && ucs < 0x0A0)) return 0;
// combining characters with zero width return intable(WIDE_EASTASIAN, ucs) ? 2 : 1;
if (intable(ZERO_WIDTH, ucs)) return 0; }
return intable(WIDE_EASTASIAN, ucs) ? 2 : 1; /**
} * The width at an index position in a java char array.
*/
/** The width at an index position in a java char array. */ public static int width(char[] chars, int index) {
public static int width(char[] chars, int index) { char c = chars[index];
char c = chars[index]; return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c);
return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c); }
}
} }

View File

@ -19,24 +19,24 @@ import io.neoterm.frontend.session.shell.ShellProfile
* @author kiva * @author kiva
*/ */
object NeoInitializer { object NeoInitializer {
fun init(context: Context) { fun init(context: Context) {
NLog.init(context) NLog.init(context)
initComponents() initComponents()
} }
fun initComponents() { fun initComponents() {
ComponentManager.registerComponent(ConfigureComponent::class.java) ComponentManager.registerComponent(ConfigureComponent::class.java)
ComponentManager.registerComponent(CodeGenComponent::class.java) ComponentManager.registerComponent(CodeGenComponent::class.java)
ComponentManager.registerComponent(ColorSchemeComponent::class.java) ComponentManager.registerComponent(ColorSchemeComponent::class.java)
ComponentManager.registerComponent(FontComponent::class.java) ComponentManager.registerComponent(FontComponent::class.java)
ComponentManager.registerComponent(UserScriptComponent::class.java) ComponentManager.registerComponent(UserScriptComponent::class.java)
ComponentManager.registerComponent(ExtraKeyComponent::class.java) ComponentManager.registerComponent(ExtraKeyComponent::class.java)
ComponentManager.registerComponent(CompletionComponent::class.java) ComponentManager.registerComponent(CompletionComponent::class.java)
ComponentManager.registerComponent(PackageComponent::class.java) ComponentManager.registerComponent(PackageComponent::class.java)
ComponentManager.registerComponent(SessionComponent::class.java) ComponentManager.registerComponent(SessionComponent::class.java)
ComponentManager.registerComponent(ProfileComponent::class.java) ComponentManager.registerComponent(ProfileComponent::class.java)
val profileComp = ComponentManager.getComponent<ProfileComponent>() val profileComp = ComponentManager.getComponent<ProfileComponent>()
profileComp.registerProfile(ShellProfile.PROFILE_META_NAME, ShellProfile::class.java) profileComp.registerProfile(ShellProfile.PROFILE_META_NAME, ShellProfile::class.java)
} }
} }

View File

@ -8,18 +8,18 @@ import io.neoterm.frontend.component.NeoComponent
* @author kiva * @author kiva
*/ */
class CodeGenComponent : NeoComponent { class CodeGenComponent : NeoComponent {
override fun onServiceInit() { override fun onServiceInit() {
} }
override fun onServiceDestroy() { override fun onServiceDestroy() {
} }
override fun onServiceObtained() { override fun onServiceObtained() {
} }
fun newGenerator(codeObject: CodeGenObject): CodeGenerator { fun newGenerator(codeObject: CodeGenObject): CodeGenerator {
val parameter = CodeGenParameter() val parameter = CodeGenParameter()
return codeObject.getCodeGenerator(parameter) return codeObject.getCodeGenerator(parameter)
} }
} }

View File

@ -11,50 +11,54 @@ import io.neoterm.frontend.component.ComponentManager
* @author kiva * @author kiva
*/ */
class NeoColorGenerator(parameter: CodeGenParameter) : CodeGenerator(parameter) { class NeoColorGenerator(parameter: CodeGenParameter) : CodeGenerator(parameter) {
override fun getGeneratorName(): String { override fun getGeneratorName(): String {
return "NeoColorScheme-Generator" return "NeoColorScheme-Generator"
}
override fun generateCode(codeGenObject: CodeGenObject): String {
if (codeGenObject !is NeoColorScheme) {
throw RuntimeException("Invalid object type, expected NeoColorScheme, got ${codeGenObject.javaClass.simpleName}")
} }
override fun generateCode(codeGenObject: CodeGenObject): String { return buildString {
if (codeGenObject !is NeoColorScheme) { start(this)
throw RuntimeException("Invalid object type, expected NeoColorScheme, got ${codeGenObject.javaClass.simpleName}") generateMetaData(this, codeGenObject)
} generateColors(this, codeGenObject)
end(this)
}
}
return buildString { private fun start(builder: StringBuilder) {
start(this) builder.append("${NeoColorScheme.CONTEXT_META_NAME}: {\n")
generateMetaData(this, codeGenObject) }
generateColors(this, codeGenObject)
end(this) private fun end(builder: StringBuilder) {
} builder.append("}\n")
}
private fun generateMetaData(builder: StringBuilder, colorScheme: NeoColorScheme) {
val component = ComponentManager.getComponent<ConfigureComponent>()
builder.append(" ${NeoColorScheme.COLOR_META_NAME}: \"${colorScheme.colorName}\"\n")
builder.append(
" ${NeoColorScheme.COLOR_META_VERSION}: ${
colorScheme.colorVersion
?: component.getLoaderVersion()
}\n"
)
builder.append("\n")
}
private fun generateColors(builder: StringBuilder, colorScheme: NeoColorScheme) {
builder.append(" ${NeoColorScheme.CONTEXT_COLOR_NAME}: {\n")
builder.append(" ${NeoColorScheme.COLOR_DEF_BACKGROUND}: ${colorScheme.backgroundColor}\n")
builder.append(" ${NeoColorScheme.COLOR_DEF_FOREGROUND}: ${colorScheme.foregroundColor}\n")
builder.append(" ${NeoColorScheme.COLOR_DEF_CURSOR}: ${colorScheme.cursorColor}\n")
colorScheme.color.entries.forEach {
builder.append(" ${NeoColorScheme.COLOR_PREFIX}${it.key}: ${it.value}\n")
} }
private fun start(builder: StringBuilder) { builder.append(" }\n")
builder.append("${NeoColorScheme.CONTEXT_META_NAME}: {\n") }
}
private fun end(builder: StringBuilder) {
builder.append("}\n")
}
private fun generateMetaData(builder: StringBuilder, colorScheme: NeoColorScheme) {
val component = ComponentManager.getComponent<ConfigureComponent>()
builder.append(" ${NeoColorScheme.COLOR_META_NAME}: \"${colorScheme.colorName}\"\n")
builder.append(" ${NeoColorScheme.COLOR_META_VERSION}: ${colorScheme.colorVersion
?: component.getLoaderVersion()}\n")
builder.append("\n")
}
private fun generateColors(builder: StringBuilder, colorScheme: NeoColorScheme) {
builder.append(" ${NeoColorScheme.CONTEXT_COLOR_NAME}: {\n")
builder.append(" ${NeoColorScheme.COLOR_DEF_BACKGROUND}: ${colorScheme.backgroundColor}\n")
builder.append(" ${NeoColorScheme.COLOR_DEF_FOREGROUND}: ${colorScheme.foregroundColor}\n")
builder.append(" ${NeoColorScheme.COLOR_DEF_CURSOR}: ${colorScheme.cursorColor}\n")
colorScheme.color.entries.forEach {
builder.append(" ${NeoColorScheme.COLOR_PREFIX}${it.key}: ${it.value}\n")
}
builder.append(" }\n")
}
} }

View File

@ -8,11 +8,11 @@ import io.neoterm.component.codegen.interfaces.CodeGenerator
* @author kiva * @author kiva
*/ */
class NeoProfileGenerator(parameter: CodeGenParameter) : CodeGenerator(parameter) { class NeoProfileGenerator(parameter: CodeGenParameter) : CodeGenerator(parameter) {
override fun getGeneratorName(): String { override fun getGeneratorName(): String {
return "NeoProfile-Generator" return "NeoProfile-Generator"
} }
override fun generateCode(codeGenObject: CodeGenObject): String { override fun generateCode(codeGenObject: CodeGenObject): String {
return "" return ""
} }
} }

View File

@ -6,5 +6,5 @@ import io.neoterm.component.codegen.CodeGenParameter
* @author kiva * @author kiva
*/ */
interface CodeGenObject { interface CodeGenObject {
fun getCodeGenerator(parameter: CodeGenParameter): CodeGenerator fun getCodeGenerator(parameter: CodeGenParameter): CodeGenerator
} }

View File

@ -6,7 +6,7 @@ import io.neoterm.component.codegen.CodeGenParameter
* @author kiva * @author kiva
*/ */
abstract class CodeGenerator(parameter: CodeGenParameter) { abstract class CodeGenerator(parameter: CodeGenParameter) {
abstract fun getGeneratorName(): String abstract fun getGeneratorName(): String
abstract fun generateCode(codeGenObject: CodeGenObject): String abstract fun generateCode(codeGenObject: CodeGenObject): String
} }

View File

@ -20,111 +20,112 @@ import java.nio.file.Files
* @author kiva * @author kiva
*/ */
class ColorSchemeComponent : ConfigFileBasedComponent<NeoColorScheme>(NeoTermPath.COLORS_PATH) { class ColorSchemeComponent : ConfigFileBasedComponent<NeoColorScheme>(NeoTermPath.COLORS_PATH) {
companion object { companion object {
fun colorFile(colorName: String): File { fun colorFile(colorName: String): File {
return File("${NeoTermPath.COLORS_PATH}/$colorName.nl") return File("${NeoTermPath.COLORS_PATH}/$colorName.nl")
} }
}
override val checkComponentFileWhenObtained
get() = true
private lateinit var DEFAULT_COLOR: NeoColorScheme
private var colors: MutableMap<String, NeoColorScheme> = mutableMapOf()
override fun onCheckComponentFiles() {
val defaultColorFile = colorFile(DefaultColorScheme.colorName)
if (!defaultColorFile.exists()) {
if (!extractDefaultColor(App.get())) {
DEFAULT_COLOR = DefaultColorScheme
colors[DEFAULT_COLOR.colorName] = DEFAULT_COLOR
return
}
} }
override val checkComponentFileWhenObtained if (!reloadColorSchemes()) {
get() = true DEFAULT_COLOR = DefaultColorScheme
colors[DEFAULT_COLOR.colorName] = DEFAULT_COLOR
}
}
private lateinit var DEFAULT_COLOR: NeoColorScheme override fun onCreateComponentObject(configVisitor: ConfigVisitor) = NeoColorScheme()
private var colors: MutableMap<String, NeoColorScheme> = mutableMapOf()
override fun onCheckComponentFiles() { fun reloadColorSchemes(): Boolean {
val defaultColorFile = colorFile(DefaultColorScheme.colorName) colors.clear()
if (!defaultColorFile.exists()) {
if (!extractDefaultColor(App.get())) {
DEFAULT_COLOR = DefaultColorScheme
colors[DEFAULT_COLOR.colorName] = DEFAULT_COLOR
return
}
}
if (!reloadColorSchemes()) { File(baseDir)
DEFAULT_COLOR = DefaultColorScheme .listFiles(NEOLANG_FILTER)
colors[DEFAULT_COLOR.colorName] = DEFAULT_COLOR .mapNotNull { this.loadConfigure(it) }
} .forEach {
colors.put(it.colorName, it)
}
if (colors.containsKey(DefaultColorScheme.colorName)) {
DEFAULT_COLOR = colors[DefaultColorScheme.colorName]!!
return true
}
return false
}
fun applyColorScheme(view: TerminalView?, extraKeysView: ExtraKeysView?, colorScheme: NeoColorScheme?) {
colorScheme?.applyColorScheme(view, extraKeysView)
}
fun getCurrentColorScheme(): NeoColorScheme {
return colors[getCurrentColorSchemeName()]!!
}
fun getCurrentColorSchemeName(): String {
var currentColorName =
NeoPreference.loadString(R.string.key_customization_color_scheme, DefaultColorScheme.colorName)
if (!colors.containsKey(currentColorName)) {
currentColorName = DefaultColorScheme.colorName
NeoPreference.store(R.string.key_customization_color_scheme, DefaultColorScheme.colorName)
}
return currentColorName
}
fun getColorScheme(colorName: String): NeoColorScheme {
return if (colors.containsKey(colorName)) colors[colorName]!! else getCurrentColorScheme()
}
fun getColorSchemeNames(): List<String> {
val list = ArrayList<String>()
list += colors.keys
return list
}
fun setCurrentColorScheme(colorName: String) {
NeoPreference.store(R.string.key_customization_color_scheme, colorName)
}
fun setCurrentColorScheme(color: NeoColorScheme) {
setCurrentColorScheme(color.colorName)
}
private fun extractDefaultColor(context: Context): Boolean {
try {
AssetsUtils.extractAssetsDir(context, "colors", baseDir)
return true
} catch (e: Exception) {
NLog.e("ColorScheme", "Failed to extract default colors: ${e.localizedMessage}")
return false
}
}
fun saveColorScheme(colorScheme: NeoColorScheme) {
val colorFile = colorFile(colorScheme.colorName)
if (colorFile.exists()) {
throw RuntimeException("ColorScheme already ${colorScheme.colorName} exists!")
} }
override fun onCreateComponentObject(configVisitor: ConfigVisitor) = NeoColorScheme() val component = ComponentManager.getComponent<CodeGenComponent>()
val content = component.newGenerator(colorScheme).generateCode(colorScheme)
fun reloadColorSchemes(): Boolean { kotlin.runCatching {
colors.clear() Files.write(colorFile.toPath(), content.toByteArray())
}.onFailure {
File(baseDir) throw RuntimeException("Failed to save file ${colorFile.absolutePath}")
.listFiles(NEOLANG_FILTER)
.mapNotNull { this.loadConfigure(it) }
.forEach {
colors.put(it.colorName, it)
}
if (colors.containsKey(DefaultColorScheme.colorName)) {
DEFAULT_COLOR = colors[DefaultColorScheme.colorName]!!
return true
}
return false
}
fun applyColorScheme(view: TerminalView?, extraKeysView: ExtraKeysView?, colorScheme: NeoColorScheme?) {
colorScheme?.applyColorScheme(view, extraKeysView)
}
fun getCurrentColorScheme(): NeoColorScheme {
return colors[getCurrentColorSchemeName()]!!
}
fun getCurrentColorSchemeName(): String {
var currentColorName = NeoPreference.loadString(R.string.key_customization_color_scheme, DefaultColorScheme.colorName)
if (!colors.containsKey(currentColorName)) {
currentColorName = DefaultColorScheme.colorName
NeoPreference.store(R.string.key_customization_color_scheme, DefaultColorScheme.colorName)
}
return currentColorName
}
fun getColorScheme(colorName: String): NeoColorScheme {
return if (colors.containsKey(colorName)) colors[colorName]!! else getCurrentColorScheme()
}
fun getColorSchemeNames(): List<String> {
val list = ArrayList<String>()
list += colors.keys
return list
}
fun setCurrentColorScheme(colorName: String) {
NeoPreference.store(R.string.key_customization_color_scheme, colorName)
}
fun setCurrentColorScheme(color: NeoColorScheme) {
setCurrentColorScheme(color.colorName)
}
private fun extractDefaultColor(context: Context): Boolean {
try {
AssetsUtils.extractAssetsDir(context, "colors", baseDir)
return true
} catch (e: Exception) {
NLog.e("ColorScheme", "Failed to extract default colors: ${e.localizedMessage}")
return false
}
}
fun saveColorScheme(colorScheme: NeoColorScheme) {
val colorFile = colorFile(colorScheme.colorName)
if (colorFile.exists()) {
throw RuntimeException("ColorScheme already ${colorScheme.colorName} exists!")
}
val component = ComponentManager.getComponent<CodeGenComponent>()
val content = component.newGenerator(colorScheme).generateCode(colorScheme)
kotlin.runCatching {
Files.write(colorFile.toPath(), content.toByteArray())
}.onFailure {
throw RuntimeException("Failed to save file ${colorFile.absolutePath}")
}
} }
}
} }

View File

@ -4,12 +4,12 @@ package io.neoterm.component.colorscheme
* @author kiva * @author kiva
*/ */
object DefaultColorScheme : NeoColorScheme() { object DefaultColorScheme : NeoColorScheme() {
init { init {
/* NOTE: Keep in sync with assets/colors/Default.nl */ /* NOTE: Keep in sync with assets/colors/Default.nl */
colorName = "Default" colorName = "Default"
foregroundColor = "#ffffff" foregroundColor = "#ffffff"
backgroundColor = "#14181c" backgroundColor = "#14181c"
cursorColor = "#a9aaa9" cursorColor = "#a9aaa9"
} }
} }

View File

@ -21,176 +21,176 @@ import java.io.File
* @author kiva * @author kiva
*/ */
open class NeoColorScheme : CodeGenObject, ConfigFileBasedObject { open class NeoColorScheme : CodeGenObject, ConfigFileBasedObject {
companion object { companion object {
const val COLOR_PREFIX = "color" const val COLOR_PREFIX = "color"
const val CONTEXT_COLOR_NAME = "colors" const val CONTEXT_COLOR_NAME = "colors"
const val CONTEXT_META_NAME = "color-scheme" const val CONTEXT_META_NAME = "color-scheme"
const val COLOR_META_NAME = "name" const val COLOR_META_NAME = "name"
const val COLOR_META_VERSION = "version" const val COLOR_META_VERSION = "version"
const val COLOR_DEF_BACKGROUND = "background" const val COLOR_DEF_BACKGROUND = "background"
const val COLOR_DEF_FOREGROUND = "foreground" const val COLOR_DEF_FOREGROUND = "foreground"
const val COLOR_DEF_CURSOR = "cursor" const val COLOR_DEF_CURSOR = "cursor"
val COLOR_META_PATH = arrayOf(CONTEXT_META_NAME) val COLOR_META_PATH = arrayOf(CONTEXT_META_NAME)
val COLOR_PATH = arrayOf(CONTEXT_META_NAME, CONTEXT_COLOR_NAME) val COLOR_PATH = arrayOf(CONTEXT_META_NAME, CONTEXT_COLOR_NAME)
const val COLOR_TYPE_BEGIN = -3 const val COLOR_TYPE_BEGIN = -3
const val COLOR_TYPE_END = 15 const val COLOR_TYPE_END = 15
const val COLOR_BACKGROUND = -3 const val COLOR_BACKGROUND = -3
const val COLOR_FOREGROUND = -2 const val COLOR_FOREGROUND = -2
const val COLOR_CURSOR = -1 const val COLOR_CURSOR = -1
const val COLOR_DIM_BLACK = 0 const val COLOR_DIM_BLACK = 0
const val COLOR_DIM_RED = 1 const val COLOR_DIM_RED = 1
const val COLOR_DIM_GREEN = 2 const val COLOR_DIM_GREEN = 2
const val COLOR_DIM_YELLOW = 3 const val COLOR_DIM_YELLOW = 3
const val COLOR_DIM_BLUE = 4 const val COLOR_DIM_BLUE = 4
const val COLOR_DIM_MAGENTA = 5 const val COLOR_DIM_MAGENTA = 5
const val COLOR_DIM_CYAN = 6 const val COLOR_DIM_CYAN = 6
const val COLOR_DIM_WHITE = 7 const val COLOR_DIM_WHITE = 7
const val COLOR_BRIGHT_BLACK = 8 const val COLOR_BRIGHT_BLACK = 8
const val COLOR_BRIGHT_RED = 9 const val COLOR_BRIGHT_RED = 9
const val COLOR_BRIGHT_GREEN = 10 const val COLOR_BRIGHT_GREEN = 10
const val COLOR_BRIGHT_YELLOW = 11 const val COLOR_BRIGHT_YELLOW = 11
const val COLOR_BRIGHT_BLUE = 12 const val COLOR_BRIGHT_BLUE = 12
const val COLOR_BRIGHT_MAGENTA = 13 const val COLOR_BRIGHT_MAGENTA = 13
const val COLOR_BRIGHT_CYAN = 14 const val COLOR_BRIGHT_CYAN = 14
const val COLOR_BRIGHT_WHITE = 15 const val COLOR_BRIGHT_WHITE = 15
}
lateinit var colorName: String
var colorVersion: String? = null
var foregroundColor: String? = null
var backgroundColor: String? = null
var cursorColor: String? = null
var color: MutableMap<Int, String> = mutableMapOf()
fun setColor(type: Int, color: String) {
if (type < 0) {
when (type) {
COLOR_BACKGROUND -> backgroundColor = color
COLOR_FOREGROUND -> foregroundColor = color
COLOR_CURSOR -> cursorColor = color
}
return
} }
this.color[type] = color
}
lateinit var colorName: String fun getColor(type: Int): String? {
var colorVersion: String? = null validateColors()
return when (type) {
var foregroundColor: String? = null COLOR_BACKGROUND -> backgroundColor
var backgroundColor: String? = null COLOR_FOREGROUND -> foregroundColor
var cursorColor: String? = null COLOR_CURSOR -> cursorColor
var color: MutableMap<Int, String> = mutableMapOf() else -> {
if (type in (0 until color.size)) {
fun setColor(type: Int, color: String) { color[type]
if (type < 0) { } else {
when (type) { ""
COLOR_BACKGROUND -> backgroundColor = color
COLOR_FOREGROUND -> foregroundColor = color
COLOR_CURSOR -> cursorColor = color
}
return
} }
this.color[type] = color }
} }
}
fun getColor(type: Int): String? { fun copy(): NeoColorScheme {
validateColors() val copy = NeoColorScheme()
return when (type) { copy.colorName = colorName
COLOR_BACKGROUND -> backgroundColor copy.backgroundColor = backgroundColor
COLOR_FOREGROUND -> foregroundColor copy.foregroundColor = foregroundColor
COLOR_CURSOR -> cursorColor copy.cursorColor = cursorColor
else -> { this.color.forEach { copy.color.put(it.key, it.value) }
if (type in (0 until color.size)) { return copy
color[type] }
} else {
""
}
}
}
}
fun copy(): NeoColorScheme { @Throws(RuntimeException::class)
val copy = NeoColorScheme() override fun onConfigLoaded(configVisitor: ConfigVisitor) {
copy.colorName = colorName val colorName = getMetaByVisitor(configVisitor, COLOR_META_NAME)
copy.backgroundColor = backgroundColor ?: throw RuntimeException("ColorScheme must have a name")
copy.foregroundColor = foregroundColor
copy.cursorColor = cursorColor
this.color.forEach { copy.color.put(it.key, it.value) }
return copy
}
@Throws(RuntimeException::class) this.colorName = colorName
override fun onConfigLoaded(configVisitor: ConfigVisitor) { this.colorVersion = getMetaByVisitor(configVisitor, COLOR_META_VERSION)
val colorName = getMetaByVisitor(configVisitor, COLOR_META_NAME)
?: throw RuntimeException("ColorScheme must have a name")
this.colorName = colorName backgroundColor = getColorByVisitor(configVisitor, "background")
this.colorVersion = getMetaByVisitor(configVisitor, COLOR_META_VERSION) foregroundColor = getColorByVisitor(configVisitor, "foreground")
cursorColor = getColorByVisitor(configVisitor, "cursor")
backgroundColor = getColorByVisitor(configVisitor, "background") configVisitor.getContext(COLOR_PATH).getAttributes().forEach {
foregroundColor = getColorByVisitor(configVisitor, "foreground") if (it.key.startsWith(COLOR_PREFIX)) {
cursorColor = getColorByVisitor(configVisitor, "cursor") val colorIndex = try {
configVisitor.getContext(COLOR_PATH).getAttributes().forEach { it.key.substringAfter(COLOR_PREFIX).toInt()
if (it.key.startsWith(COLOR_PREFIX)) {
val colorIndex = try {
it.key.substringAfter(COLOR_PREFIX).toInt()
} catch (e: Exception) {
-1
}
if (colorIndex == -1) {
NLog.w("ColorScheme", "Invalid color type: ${it.key}")
} else {
setColor(colorIndex, it.value.asString())
}
}
}
validateColors()
}
internal fun applyColorScheme(view: TerminalView?, extraKeysView: ExtraKeysView?) {
validateColors()
if (view != null) {
val scheme = TerminalColorScheme()
scheme.updateWith(foregroundColor, backgroundColor, cursorColor, color)
val session = view.currentSession
if (session != null && session.emulator != null) {
session.emulator.setColorScheme(scheme)
}
view.setBackgroundColor(TerminalColors.parse(backgroundColor))
}
if (extraKeysView != null) {
extraKeysView.setBackgroundColor(TerminalColors.parse(backgroundColor))
extraKeysView.setTextColor(TerminalColors.parse(foregroundColor))
}
}
override fun getCodeGenerator(parameter: CodeGenParameter): CodeGenerator {
return NeoColorGenerator(parameter)
}
private fun validateColors() {
backgroundColor = backgroundColor ?: DefaultColorScheme.backgroundColor
foregroundColor = foregroundColor ?: DefaultColorScheme.foregroundColor
cursorColor = cursorColor ?: DefaultColorScheme.cursorColor
}
private fun getMetaByVisitor(visitor: ConfigVisitor, metaName: String): String? {
return visitor.getStringValue(COLOR_META_PATH, metaName)
}
private fun getColorByVisitor(visitor: ConfigVisitor, colorName: String): String? {
return visitor.getStringValue(COLOR_PATH, colorName)
}
@TestOnly
fun testLoadConfigure(file: File): Boolean {
val loaderService = ComponentManager.getComponent<ConfigureComponent>()
val configure: NeoConfigureFile?
try {
configure = loaderService.newLoader(file).loadConfigure()
if (configure == null) {
throw RuntimeException("Parse configuration failed.")
}
} catch (e: Exception) { } catch (e: Exception) {
NLog.e("ExtraKey", "Failed to load extra key config: ${file.absolutePath}: ${e.localizedMessage}") -1
return false
} }
val visitor = configure.getVisitor() if (colorIndex == -1) {
onConfigLoaded(visitor) NLog.w("ColorScheme", "Invalid color type: ${it.key}")
return true } else {
setColor(colorIndex, it.value.asString())
}
}
} }
validateColors()
}
internal fun applyColorScheme(view: TerminalView?, extraKeysView: ExtraKeysView?) {
validateColors()
if (view != null) {
val scheme = TerminalColorScheme()
scheme.updateWith(foregroundColor, backgroundColor, cursorColor, color)
val session = view.currentSession
if (session != null && session.emulator != null) {
session.emulator.setColorScheme(scheme)
}
view.setBackgroundColor(TerminalColors.parse(backgroundColor))
}
if (extraKeysView != null) {
extraKeysView.setBackgroundColor(TerminalColors.parse(backgroundColor))
extraKeysView.setTextColor(TerminalColors.parse(foregroundColor))
}
}
override fun getCodeGenerator(parameter: CodeGenParameter): CodeGenerator {
return NeoColorGenerator(parameter)
}
private fun validateColors() {
backgroundColor = backgroundColor ?: DefaultColorScheme.backgroundColor
foregroundColor = foregroundColor ?: DefaultColorScheme.foregroundColor
cursorColor = cursorColor ?: DefaultColorScheme.cursorColor
}
private fun getMetaByVisitor(visitor: ConfigVisitor, metaName: String): String? {
return visitor.getStringValue(COLOR_META_PATH, metaName)
}
private fun getColorByVisitor(visitor: ConfigVisitor, colorName: String): String? {
return visitor.getStringValue(COLOR_PATH, colorName)
}
@TestOnly
fun testLoadConfigure(file: File): Boolean {
val loaderService = ComponentManager.getComponent<ConfigureComponent>()
val configure: NeoConfigureFile?
try {
configure = loaderService.newLoader(file).loadConfigure()
if (configure == null) {
throw RuntimeException("Parse configuration failed.")
}
} catch (e: Exception) {
NLog.e("ExtraKey", "Failed to load extra key config: ${file.absolutePath}: ${e.localizedMessage}")
return false
}
val visitor = configure.getVisitor()
onConfigLoaded(visitor)
return true
}
} }

Some files were not shown because too many files have changed in this diff Show More