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
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.
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.
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. 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
@ -26,23 +30,35 @@ Examples of unacceptable behavior by participants include:
## 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
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
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
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
[version]: http://contributor-covenant.org/version/1/4/

View File

@ -1,5 +1,6 @@
# Contributing
# How to
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'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile rootProject.ext.deps["kotlin-stdlib"]
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile rootProject.ext.deps["kotlin-stdlib"]
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath rootProject.ext.deps["kotlin-gradle-plugin"]
}
repositories {
mavenCentral()
}
dependencies {
classpath rootProject.ext.deps["kotlin-gradle-plugin"]
}
}
repositories {
mavenCentral()
mavenCentral()
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
kotlinOptions {
jvmTarget = "1.8"
}
}
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) {
var lineNumber = 0
var lineNumber = 0
override fun toString(): String {
return "Token { tokenType: $tokenType, tokenValue: $tokenValue };"
}
override fun toString(): String {
return "Token { tokenType: $tokenType, tokenValue: $tokenValue };"
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import io.neolang.ast.base.NeoLangBaseNode
* @author kiva
*/
open class NeoLangAstBasedNode(val ast: NeoLangBaseNode) : NeoLangBaseNode() {
override fun toString(): String {
return "${javaClass.simpleName} { ast: $ast }"
}
override fun toString(): String {
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() {
override fun toString(): String {
return "NeoLangAttributeNode { stringNode: $stringNode, block: $blockNode }"
}
override fun toString(): String {
return "NeoLangAttributeNode { stringNode: $stringNode, block: $blockNode }"
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,87 +18,87 @@ import io.neolang.runtime.type.NeoLangValue
* @author kiva
*/
internal object AstVisitorImpl {
fun visitProgram(ast: NeoLangProgramNode, visitorCallback: IVisitorCallback) {
visitorCallback.onStart()
ast.groups.forEach { visitGroup(it, visitorCallback) }
visitorCallback.onFinish()
fun visitProgram(ast: NeoLangProgramNode, visitorCallback: IVisitorCallback) {
visitorCallback.onStart()
ast.groups.forEach { visitGroup(it, visitorCallback) }
visitorCallback.onFinish()
}
fun visitGroup(ast: NeoLangGroupNode, visitorCallback: IVisitorCallback) {
ast.attributes.forEach {
visitAttribute(it, visitorCallback)
}
}
fun visitGroup(ast: NeoLangGroupNode, visitorCallback: IVisitorCallback) {
ast.attributes.forEach {
visitAttribute(it, visitorCallback)
}
}
fun visitAttribute(ast: NeoLangAttributeNode, visitorCallback: IVisitorCallback) {
visitBlock(ast.blockNode, ast.stringNode.eval().asString(), visitorCallback)
}
fun visitAttribute(ast: NeoLangAttributeNode, visitorCallback: IVisitorCallback) {
visitBlock(ast.blockNode, ast.stringNode.eval().asString(), visitorCallback)
}
fun visitArray(ast: NeoLangArrayNode, visitorCallback: IVisitorCallback) {
val arrayName = ast.arrayNameNode.eval().asString()
fun visitArray(ast: NeoLangArrayNode, visitorCallback: IVisitorCallback) {
val arrayName = ast.arrayNameNode.eval().asString()
visitorCallback.onEnterContext(arrayName)
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)
}
}
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()
}
is NeoLangStringNode -> {
definePrimaryData(index.toString(), visitingNode.eval(), visitorCallback)
}
is NeoLangNumberNode -> {
definePrimaryData(index.toString(), visitingNode.eval(), visitorCallback)
}
}
}
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()
}
is NeoLangStringNode -> {
definePrimaryData(index.toString(), visitingNode.eval(), visitorCallback)
}
is NeoLangNumberNode -> {
definePrimaryData(index.toString(), visitingNode.eval(), visitorCallback)
}
}
fun visitBlock(ast: NeoLangBlockNode, blockName: String, visitorCallback: IVisitorCallback) {
val visitingNode = ast.ast
when (visitingNode) {
is NeoLangGroupNode -> {
// is a sub block, e.g.
// block: { $blockName: {} }
visitorCallback.onEnterContext(blockName)
AstVisitorImpl.visitGroup(visitingNode, visitorCallback)
visitorCallback.onExitContext()
}
is NeoLangArrayNode -> {
// 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)
}
}
}
fun visitBlock(ast: NeoLangBlockNode, blockName: String, visitorCallback: IVisitorCallback) {
val visitingNode = ast.ast
when (visitingNode) {
is NeoLangGroupNode -> {
// is a sub block, e.g.
// block: { $blockName: {} }
private fun definePrimaryData(name: String, value: NeoLangValue, visitorCallback: IVisitorCallback) {
visitorCallback.getCurrentContext().defineAttribute(name, value)
}
visitorCallback.onEnterContext(blockName)
AstVisitorImpl.visitGroup(visitingNode, visitorCallback)
visitorCallback.onExitContext()
}
is NeoLangArrayNode -> {
// 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)
}
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
*/
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
*/
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 {
throw RuntimeException("getCurrentContext() not supported in this IVisitorCallback!")
}
override fun getCurrentContext(): NeoLangContext {
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) {
fun getVisitor(callbackInterface: Class<out IVisitorCallback>): AstVisitor? {
try {
return AstVisitor(ast, callbackInterface.newInstance())
} catch (e: Exception) {
return null
}
fun getVisitor(callbackInterface: Class<out IVisitorCallback>): AstVisitor? {
try {
return AstVisitor(ast, callbackInterface.newInstance())
} catch (e: Exception) {
return null
}
}
}

View File

@ -8,33 +8,33 @@ import java.io.FileInputStream
* @author kiva
*/
class Main {
companion object {
@JvmStatic
fun main(args: Array<String>) {
if (args.isEmpty()) {
println("Usage: NeoLang <program.nl>")
return
}
companion object {
@JvmStatic
fun main(args: Array<String>) {
if (args.isEmpty()) {
println("Usage: NeoLang <program.nl>")
return
}
val parser = NeoLangParser()
args.forEach {
val programCode = readFully(it)
parser.setInputSource(programCode)
val ast = parser.parse()
println("Compile `$it'")
ast.visit()
.getVisitor(DisplayProcessVisitor::class.java)
?.start()
}
return
}
private fun readFully(file: String): String {
FileInputStream(file).use {
val bytes = ByteArray(it.available())
it.read(bytes)
return String(bytes)
}
}
val parser = NeoLangParser()
args.forEach {
val programCode = readFully(it)
parser.setInputSource(programCode)
val ast = parser.parse()
println("Compile `$it'")
ast.visit()
.getVisitor(DisplayProcessVisitor::class.java)
?.start()
}
return
}
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.NeoLangTokenType
import io.neolang.ast.NeoLangTokenValue
import java.util.*
/**
* grammar: [
@ -19,254 +18,255 @@ import java.util.*
* @author kiva
*/
class NeoLangLexer {
private var programCode: String? = null
private var currentPosition: Int = 0
private var currentChar: Char = ' '
private var lineNumber = 0
private var programCode: String? = null
private var currentPosition: Int = 0
private var currentChar: Char = ' '
private var lineNumber = 0
internal fun setInputSource(programCode: String?) {
this.programCode = programCode
internal fun setInputSource(programCode: String?) {
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> {
val programCode = this.programCode ?: return listOf()
val tokens = ArrayList<NeoLangToken>()
currentPosition = 0
lineNumber = 1
private fun getNextTokenAsString(): String {
// Skip start quote
// and a single quote is now allowed
moveToNextChar(eofThrow = true)
val builder = StringBuilder()
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
}
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
var loop = true
while (loop && currentChar != NeoLangTokenValue.QUOTE.value.asString()[0]) {
// NeoLang does not support escaped char
// if (currentChar == '\\') {
// builder.append('\\')
// moveToNextChar(eofThrow = true)
// }
builder.append(currentChar)
loop = moveToNextChar()
}
// Skip end quote
moveToNextChar()
return builder.toString()
builder.append(currentChar)
loop = moveToNextChar()
}
private fun getNextTokenAsNumber(): String {
var numberValue: Double = (currentChar.toInt() - '0'.toInt()).toDouble()
// Skip end quote
moveToNextChar()
return builder.toString()
}
// Four types of numbers are supported:
// Dec(123) Hex(0x123) Oct(017) Bin(0b11)
private fun getNextTokenAsNumber(): String {
var numberValue: Double = (currentChar.toInt() - '0'.toInt()).toDouble()
// Dec
if (numberValue > 0) {
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)
}
}
// Four types of numbers are supported:
// Dec(123) Hex(0x123) Oct(017) Bin(0b11)
// Dec
if (numberValue > 0) {
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)
}
}
private fun getNextOctalNumber(numberValue: Double): Double {
var value: Double = numberValue
var loop = true
while (loop && currentChar in ('0'..'7')) {
value = value * 8 + currentChar.toNumber()
loop = moveToNextChar()
return numberValue.toString()
}
private fun getNextOctalNumber(numberValue: Double): Double {
var value: Double = numberValue
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 {
var value = numberValue
var loop = moveToNextChar() // skip 'b' or 'B'
while (loop && currentChar in ('0'..'1')) {
value += value * 2 + currentChar.toNumber()
loop = moveToNextChar()
} else if (currentChar == '.') {
floatPointMeet = true
loop = moveToNextChar()
} else {
break
}
}
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 {
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 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 getNextDecimalNumber(numberValue: Double): Double {
var floatPointMeet = false
var floatPart: Double = 0.0
var floatNumberCounter = 1
var value = numberValue
private fun Char.toNumber(): Int {
return if (isNumber()) {
this.toInt() - '0'.toInt()
} else 0
}
var loop = moveToNextChar()
while (loop) {
if (currentChar.isNumber()) {
if (floatPointMeet) {
floatPart = floatPart * 10 + currentChar.toNumber()
floatNumberCounter *= 10
} else {
value = value * 10 + currentChar.toNumber()
}
loop = moveToNextChar()
private fun Char.isNumber(): Boolean {
return this in ('0'..'9')
}
} else if (currentChar == '.') {
floatPointMeet = true
loop = moveToNextChar()
} else {
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')
}
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
*/
class NeoLangParser {
private val lexer = NeoLangLexer()
private var tokens = mutableListOf<NeoLangToken>()
private var currentPosition: Int = 0
private var currentToken: NeoLangToken? = null
private val lexer = NeoLangLexer()
private var tokens = mutableListOf<NeoLangToken>()
private var currentPosition: Int = 0
private var currentToken: NeoLangToken? = null
fun setInputSource(programCode: String?) {
lexer.setInputSource(programCode)
fun setInputSource(programCode: String?) {
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 {
return updateParserStatus(lexer.lex()) ?: throw ParseException("AST is null")
this.tokens.clear()
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? {
if (tokens.isEmpty()) {
// Allow empty program
return NeoLangProgramNode.emptyNode()
return false
}
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
}
this.tokens.clear()
this.tokens.addAll(tokens)
currentPosition = 0
currentToken = tokens[currentPosition]
return program()
groups.add(group)
}
return NeoLangProgramNode(groups)
}
private fun match(tokenType: NeoLangTokenType, errorThrow: Boolean = false): Boolean {
val currentToken = this.currentToken ?: throw InvalidTokenException("Unexpected token: null")
return NeoLangProgramNode.emptyNode()
}
if (currentToken.tokenType === tokenType) {
currentPosition++
if (currentPosition >= tokens.size) {
this.currentToken = NeoLangToken(NeoLangTokenType.EOF, NeoLangTokenValue.EOF)
} else {
this.currentToken = tokens[currentPosition]
}
return true
/**
* @param attrName Only available when group is a attribute value
*/
private fun group(): NeoLangGroupNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
} else if (errorThrow) {
throw InvalidTokenException("Unexpected token `${currentToken.tokenValue}' typed " +
"`${currentToken.tokenType}' near line ${currentToken.lineNumber}, " +
"expected $tokenType")
var attr = attribute()
if (attr != null) {
val attributes = mutableListOf(attr)
while (token.tokenType !== NeoLangTokenType.EOF
&& token.tokenType !== NeoLangTokenType.BRACKET_END
&& token.tokenType !== NeoLangTokenType.ARRAY_END
) {
attr = attribute()
if (attr == null) {
break
}
return false
attributes.add(attr)
}
return NeoLangGroupNode(attributes.toTypedArray())
}
private fun program(): NeoLangProgramNode {
val token = currentToken
return null
}
var group = group()
if (group != null) {
val groups = mutableListOf(group)
while (token?.tokenType !== NeoLangTokenType.EOF) {
group = group()
if (group == null) {
break
}
groups.add(group)
}
return NeoLangProgramNode(groups)
private fun attribute(): NeoLangAttributeNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
if (match(NeoLangTokenType.ID)) {
match(NeoLangTokenType.COLON, errorThrow = true)
val attrName = NeoLangStringNode(token)
val block = block(attrName) ?: NeoLangBlockNode.emptyNode()
return NeoLangAttributeNode(attrName, block)
}
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())
}
/**
* @param attrName Only available when group is a attribute value
*/
private fun group(): NeoLangGroupNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
return null
}
var attr = attribute()
if (attr != null) {
val attributes = mutableListOf(attr)
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 NeoLangGroupNode(attributes.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
}
private fun attribute(): NeoLangAttributeNode? {
val token = currentToken ?: throw InvalidTokenException("Unexpected token: null")
if (match(NeoLangTokenType.ID)) {
match(NeoLangTokenType.COLON, errorThrow = true)
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)
val attrName = NeoLangStringNode(token)
// Allow empty arrays
return if (array != null) NeoLangBlockNode(array) else NeoLangBlockNode.emptyNode()
}
val block = block(attrName) ?: NeoLangBlockNode.emptyNode()
return NeoLangAttributeNode(attrName, block)
}
return null
else -> throw InvalidTokenException("Unexpected token `${token.tokenValue}' typed `${token.tokenType}' for block")
}
}
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
var block = blockNonArrayElement(arrayName)
var index = 0
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)
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 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
}
// 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
*/
class NeoLangContext(val contextName: String) {
companion object {
private val emptyContext = NeoLangContext("<Context-Empty>")
}
companion object {
private val emptyContext = NeoLangContext("<Context-Empty>")
}
private val attributes = mutableMapOf<String, NeoLangValue>()
val children = mutableListOf<NeoLangContext>()
var parent: NeoLangContext? = null
private val attributes = mutableMapOf<String, NeoLangValue>()
val children = mutableListOf<NeoLangContext>()
var parent: NeoLangContext? = null
fun defineAttribute(attributeName: String, attributeValue: NeoLangValue): NeoLangContext {
attributes[attributeName] = attributeValue
return this
}
fun defineAttribute(attributeName: String, attributeValue: NeoLangValue): NeoLangContext {
attributes[attributeName] = attributeValue
return this
}
fun getAttribute(attributeName: String): NeoLangValue {
return attributes[attributeName] ?: parent?.getAttribute(attributeName) ?: NeoLangValue.UNDEFINED
}
fun getAttribute(attributeName: String): NeoLangValue {
return attributes[attributeName] ?: parent?.getAttribute(attributeName) ?: NeoLangValue.UNDEFINED
}
fun getChild(contextName: String): NeoLangContext {
var found: NeoLangContext? = null
children.forEach {
if (it.contextName == contextName) {
found = it
}
}
return found ?: emptyContext
fun getChild(contextName: String): NeoLangContext {
var found: NeoLangContext? = null
children.forEach {
if (it.contextName == contextName) {
found = it
}
}
return found ?: emptyContext
}
fun getAttributes(): Map<String, NeoLangValue> {
return attributes
}
fun getAttributes(): Map<String, NeoLangValue> {
return attributes
}
}

View File

@ -5,55 +5,58 @@ import io.neolang.runtime.context.NeoLangContext
/**
* @author kiva
*/
class NeoLangArray private constructor(val elements: List<NeoLangArrayElement>, override val size: Int = elements.size) : Collection<NeoLangArrayElement> {
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)
}
class NeoLangArray private constructor(
val elements: List<NeoLangArrayElement>,
override val size: Int = elements.size
) : Collection<NeoLangArrayElement> {
companion object {
internal class PrimaryElement(val primaryValue: NeoLangValue) : NeoLangArrayElement() {
override fun eval(): NeoLangValue {
return primaryValue
}
}
operator fun get(index: Int): NeoLangArrayElement {
return elements[index]
internal class BlockElement(val blockContext: NeoLangContext) : NeoLangArrayElement() {
override fun eval(key: String): NeoLangValue {
return blockContext.getAttribute(key)
}
override fun isBlock(): Boolean {
return true
}
}
override fun contains(element: NeoLangArrayElement): Boolean {
return elements.contains(element)
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)
}
}
override fun containsAll(elements: Collection<NeoLangArrayElement>): Boolean {
return this.elements.containsAll(elements)
}
operator fun get(index: Int): NeoLangArrayElement {
return elements[index]
}
override fun isEmpty(): Boolean {
return size == 0
}
override fun contains(element: NeoLangArrayElement): Boolean {
return elements.contains(element)
}
override fun iterator(): Iterator<NeoLangArrayElement> {
return elements.iterator()
}
override fun containsAll(elements: Collection<NeoLangArrayElement>): Boolean {
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
*/
open class NeoLangArrayElement {
open fun eval(): NeoLangValue {
return NeoLangValue.UNDEFINED
}
open fun eval(): NeoLangValue {
return NeoLangValue.UNDEFINED
}
open fun eval(key: String): NeoLangValue {
return NeoLangValue.UNDEFINED
}
open fun eval(key: String): NeoLangValue {
return NeoLangValue.UNDEFINED
}
open fun isBlock(): Boolean {
return false
}
open fun isBlock(): Boolean {
return false
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ package io.neoterm.bridge;
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).
@ -10,8 +10,8 @@ import static org.junit.Assert.*;
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View File

@ -7,14 +7,18 @@ NeoTerm
A modern-designed android terminal emulator for the 21st century.
### 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
View on [GitBook](https://neoterm.gitbooks.io/neoterm-wiki/content)
View on [GitHub](https://github.com/NeoTerm/NeoTerm-Wiki)
### Download
[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)

View File

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

View File

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

View File

@ -27,304 +27,269 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Arrays;
@SuppressWarnings("JniMissingFunction")
class AccelerometerReader implements SensorEventListener
{
class AccelerometerReader implements SensorEventListener {
private SensorManager _manager = null;
public boolean openedBySDL = false;
public static final GyroscopeListener gyro = new GyroscopeListener();
public static final OrientationListener orientation = new OrientationListener();
private SensorManager _manager = null;
public boolean openedBySDL = false;
public static final GyroscopeListener gyro = new GyroscopeListener();
public static final OrientationListener orientation = new OrientationListener();
public AccelerometerReader(Context context)
{
_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 AccelerometerReader(Context context) {
_manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
}
public synchronized void start()
{
if( (Globals.UseAccelerometerAsArrowKeys || Globals.AppUsesAccelerometer) &&
_manager != null && _manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null )
{
Log.i("SDL", "libSDL: starting accelerometer");
_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 synchronized void stop() {
if (_manager != null) {
Log.i("SDL", "libSDL: stopping accelerometer/gyroscope/orientation");
_manager.unregisterListener(this);
_manager.unregisterListener(gyro);
_manager.unregisterListener(orientation);
}
}
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!
}
public synchronized void start() {
if ((Globals.UseAccelerometerAsArrowKeys || Globals.AppUsesAccelerometer) &&
_manager != null && _manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null) {
Log.i("SDL", "libSDL: starting accelerometer");
_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 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 boolean invertedOrientation = false;
public void onAccuracyChanged(Sensor s, int a) {
}
// Noise filter with sane initial values, so user will be able
// to move gyroscope during the first 10 seconds, while the noise is measured.
// 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 };
static class GyroscopeListener implements SensorEventListener {
public boolean invertedOrientation = false;
// The noise levels we're measuring.
// Large initial values, they will decrease, but never increase.
float noiseMin[] = new float[] { -1.0f, -1.0f, -1.0f };
float noiseMax[] = new float[] { 1.0f, 1.0f, 1.0f };
// Noise filter with sane initial values, so user will be able
// to move gyroscope during the first 10 seconds, while the noise is measured.
// 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 gyro data buffer, from which we care calculating min/max noise values.
// The bigger it is, the more precise the calclations, and the longer it takes to converge.
float noiseData[][] = new float[200][noiseMin.length];
int noiseDataIdx = 0;
// The noise levels we're measuring.
// Large initial values, they will decrease, but never increase.
float noiseMin[] = new float[]{-1.0f, -1.0f, -1.0f};
float noiseMax[] = new float[]{1.0f, 1.0f, 1.0f};
// When we detect movement, we remove last few values of the measured data.
// The movement is detected by comparing values to noiseMin/noiseMax of the previous iteration.
int movementBackoff = 0;
// The gyro data buffer, from which we care calculating min/max noise values.
// The bigger it is, the more precise the calclations, and the longer it takes to converge.
float noiseData[][] = new float[200][noiseMin.length];
int noiseDataIdx = 0;
// Difference between min/max in the previous measurement iteration,
// used to determine when we should stop measuring, when the change becomes negligilbe.
float measuredNoiseRange[] = null;
// When we detect movement, we remove last few values of the measured data.
// The movement is detected by comparing values to noiseMin/noiseMax of the previous iteration.
int movementBackoff = 0;
// How long the algorithm is running, to stop it if it does not converge.
int measurementIteration = 0;
// Difference between min/max in the previous measurement iteration,
// 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)
{
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++;
public GyroscopeListener() {
}
if( noiseDataIdx < noiseData.length )
return;
void collectNoiseData(final float[] data) {
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++;
Log.d( "SDL", "GYRO_NOISE: Measuring in progress... " + measurementIteration );
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;
}
if (noiseDataIdx < noiseData.length)
return;
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;
}
}
measurementIteration++;
Log.d("SDL", "GYRO_NOISE: Measuring in progress... " + measurementIteration);
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;
}
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 )
return;
Log.d("SDL", "GYRO_NOISE: MIN MAX: " + Arrays.toString(noiseMin) + " " + Arrays.toString(noiseMax));
// 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];
for( int i = 0; i < noiseMin.length; i++ )
range[i] = noiseMax[i] - noiseMin[i];
// Determine when to stop measuring - check that the previous min/max range is close enough to the current one
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 )
{
measuredNoiseRange = range;
return; // First iteration, skip further checks
}
Log.d("SDL", "GYRO_NOISE: RANGE: " + Arrays.toString(range) + " " + Arrays.toString(measuredNoiseRange));
for( int i = 0; i < range.length; i++ )
{
if( measuredNoiseRange[i] / range[i] > 1.2f )
{
measuredNoiseRange = range;
return;
}
}
if (measuredNoiseRange == null) {
measuredNoiseRange = range;
return; // First iteration, skip further checks
}
// We converged to the final min/max filter values, stop measuring
System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length);
System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length);
noiseData = null;
measuredNoiseRange = null;
Log.d( "SDL", "GYRO_NOISE: Measuring done! Range converged on iteration " + measurementIteration );
}
for (int i = 0; i < range.length; i++) {
if (measuredNoiseRange[i] / range[i] > 1.2f) {
measuredNoiseRange = range;
return;
}
}
public void onSensorChanged(final SensorEvent event)
{
boolean filtered = true;
final float[] data = event.values;
// We converged to the final min/max filter values, stop measuring
System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length);
System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length);
noiseData = null;
measuredNoiseRange = null;
Log.d("SDL", "GYRO_NOISE: Measuring done! Range converged on iteration " + measurementIteration);
}
if( noiseData != null )
collectNoiseData(data);
public void onSensorChanged(final SensorEvent event) {
boolean filtered = true;
final float[] data = event.values;
for( int i = 0; i < 3; i++ )
{
if( data[i] < filterMin[i] )
{
filtered = false;
data[i] -= filterMin[i];
}
else if( data[i] > filterMax[i] )
{
filtered = false;
data[i] -= filterMax[i];
}
}
if (noiseData != null)
collectNoiseData(data);
if( filtered )
return;
for (int i = 0; i < 3; i++) {
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( 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]);
}
}
if (filtered)
return;
public void onAccuracyChanged(Sensor s, int a)
{
}
public boolean available(AppCompatActivity context)
{
SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
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);
}
}
if (Globals.HorizontalOrientation) {
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]);
}
}
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)
{
}
}
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);
public boolean available(AppCompatActivity context) {
SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
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;
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.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder.AudioSource;
import java.io.*;
import android.util.Log;
import java.util.concurrent.Semaphore;
import android.Manifest;
import android.content.pm.PackageManager;
import io.neoterm.xorg.NeoXorgViewClient;
import java.util.concurrent.Semaphore;
@SuppressWarnings("JniMissingFunction")
class AudioThread
{
private NeoXorgViewClient mClient;
private AudioTrack mAudio;
private byte[] mAudioBuffer;
private int mVirtualBufSize;
class AudioThread {
private NeoXorgViewClient mClient;
private AudioTrack mAudio;
private byte[] mAudioBuffer;
private int mVirtualBufSize;
public AudioThread(NeoXorgViewClient client)
{
this.mClient = client;
mAudio = null;
mAudioBuffer = null;
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();
public AudioThread(NeoXorgViewClient client) {
this.mClient = client;
mAudio = null;
mAudioBuffer = null;
nativeAudioInitJavaCallbacks();
}
mAudio.write( mAudioBuffer, 0, mVirtualBufSize );
}
return 1;
}
public int initAudio(int rate, int channels, int encoding, int bufSize)
{
if( mAudio == null )
{
channels = ( channels == 1 ) ? AudioFormat.CHANNEL_CONFIGURATION_MONO :
AudioFormat.CHANNEL_CONFIGURATION_STEREO;
encoding = ( encoding == 1 ) ? AudioFormat.ENCODING_PCM_16BIT :
AudioFormat.ENCODING_PCM_8BIT;
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();
mVirtualBufSize = bufSize;
mAudio.write(mAudioBuffer, 0, mVirtualBufSize);
}
if( AudioTrack.getMinBufferSize( rate, channels, encoding ) > bufSize )
bufSize = AudioTrack.getMinBufferSize( rate, channels, encoding );
return 1;
}
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];
public int initAudio(int rate, int channels, int encoding, int bufSize) {
if (mAudio == null) {
channels = (channels == 1) ? AudioFormat.CHANNEL_CONFIGURATION_MONO :
AudioFormat.CHANNEL_CONFIGURATION_STEREO;
encoding = (encoding == 1) ? AudioFormat.ENCODING_PCM_16BIT :
AudioFormat.ENCODING_PCM_8BIT;
mAudio = new AudioTrack(AudioManager.STREAM_MUSIC,
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;
}
mVirtualBufSize = bufSize;
public int resumeAudioPlayback()
{
if( mAudio != null )
{
mAudio.play();
}
if( mRecordThread != null )
{
mRecordThread.resumeRecording();
}
return 1;
}
if (AudioTrack.getMinBufferSize(rate, channels, encoding) > bufSize)
bufSize = AudioTrack.getMinBufferSize(rate, channels, encoding);
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;
private AudioRecord mRecorder = null;
private int mRecorderBufferSize = 0;
public byte[] getBuffer() {
return mAudioBuffer;
}
private byte[] startRecording(int rate, int channels, int encoding, int bufsize)
{
if( mRecordThread == null )
{
mRecordThread = new RecordingThread();
mRecordThread.start();
}
if( !mRecordThread.isStopped() )
{
Log.i("SDL", "SDL: error: application already opened audio recording device");
return null;
}
public int deinitAudio() {
if (mAudio != null) {
mAudio.stop();
mAudio.release();
mAudio = null;
}
mAudioBuffer = null;
return 1;
}
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 :
AudioFormat.CHANNEL_IN_STEREO;
int encodingConfig = ( encoding == 1 ) ? AudioFormat.ENCODING_PCM_16BIT :
AudioFormat.ENCODING_PCM_8BIT;
public int pauseAudioPlayback() {
if (mAudio != null) {
mAudio.pause();
}
if (mRecordThread != null) {
mRecordThread.pauseRecording();
}
return 1;
}
int minBufDevice = AudioRecord.getMinBufferSize(rate, channelConfig, encodingConfig);
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 int resumeAudioPlayback() {
if (mAudio != null) {
mAudio.play();
}
if (mRecordThread != null) {
mRecordThread.resumeRecording();
}
return 1;
}
private void stopRecording()
{
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 native int nativeAudioInitJavaCallbacks();
private class RecordingThread extends Thread
{
private boolean stopped = true;
byte[] mRecordBuffer;
private Semaphore waitStarted = new Semaphore(0);
private boolean sleep = false;
// ----- Audio recording -----
RecordingThread()
{
super();
}
private RecordingThread mRecordThread = null;
private AudioRecord mRecorder = null;
private int mRecorderBufferSize = 0;
void init(int bufsize)
{
if( mRecordBuffer == null || mRecordBuffer.length != bufsize )
mRecordBuffer = new byte[bufsize];
}
private byte[] startRecording(int rate, int channels, int encoding, int bufsize) {
if (mRecordThread == null) {
mRecordThread = new RecordingThread();
mRecordThread.start();
}
if (!mRecordThread.isStopped()) {
Log.i("SDL", "SDL: error: application already opened audio recording device");
return null;
}
public void run()
{
while( true )
{
waitStarted.acquireUninterruptibly();
waitStarted.drainPermits();
stopped = false;
sleep = false;
mRecordThread.init(bufsize);
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");
}
}
int channelConfig = (channels == 1) ? AudioFormat.CHANNEL_IN_MONO :
AudioFormat.CHANNEL_IN_STEREO;
int encodingConfig = (encoding == 1) ? AudioFormat.ENCODING_PCM_16BIT :
AudioFormat.ENCODING_PCM_8BIT;
stopped = true;
mRecorder.stop();
}
}
int minBufDevice = AudioRecord.getMinBufferSize(rate, channelConfig, encodingConfig);
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()
{
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 void stopRecording() {
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 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;
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.OnPrimaryClipChangedListener;
import android.app.PendingIntent;
import android.app.AlarmManager;
import android.content.Intent;
import android.view.View;
import android.view.Display;
import android.content.Context;
import android.os.Build;
import android.util.Log;
public abstract class Clipboard
{
public static Clipboard get()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
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);
public abstract class Clipboard {
public static Clipboard get() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
return NewerClipboard.Holder.Instance;
return OlderClipboard.Holder.Instance;
}
private static class NewerClipboard extends Clipboard
{
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();
}
});
}
}
public abstract void set(final Context context, final String text);
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");
}
}
public abstract String get(final Context context);
public abstract void setListener(final Context context, final Runnable listener);
private static class NewerClipboard extends Clipboard {
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 {
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;
public class Globals {
public static String XLIB_DIR = "/data/data/io.neoterm/files/usr/lib/xorg-neoterm";
public static String XLIBS[] = {
"x11_sdl_native_helpers",
"x11_sdl-1.2",
"x11_sdl_ttf",
"x11_crypto",
};
public static String XAPP_LIBS[] = {
"x11_application",
"x11_sdl_main",
};
public static String XLIB_DIR = "/data/data/io.neoterm/files/usr/lib/xorg-neoterm";
public static String XLIBS[] = {
"x11_sdl_native_helpers",
"x11_sdl-1.2",
"x11_sdl_ttf",
"x11_crypto",
};
public static String XAPP_LIBS[] = {
"x11_application",
"x11_sdl_main",
};
// 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 final boolean Using_SDL_1_3 = 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 boolean SwVideoMode = true;
public static boolean NeedDepthBuffer = false;
public static boolean NeedStencilBuffer = false;
public static boolean NeedGles2 = false;
public static boolean NeedGles3 = false;
public static boolean CompatibilityHacksVideo = false;
public static boolean CompatibilityHacksForceScreenUpdateMouseClick = true;
public static boolean CompatibilityHacksStaticInit = false;
public static boolean CompatibilityHacksTextInputEmulatesHwKeyboard = true;
public static int TextInputKeyboard = 0;
public static boolean KeepAspectRatioDefaultSetting = false;
public static boolean InhibitSuspend = true;
public static boolean CreateService = true;
public static String ReadmeText = "";
public static String CommandLine = "XSDL";
public static boolean AppUsesMouse = true;
public static boolean AppNeedsTwoButtonMouse = true;
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 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 AppNeedsArrowKeys = false;
public static boolean AppNeedsTextInput = false;
public static boolean AppUsesJoystick = false;
public static boolean AppUsesSecondJoystick = false;
public static boolean AppUsesThirdJoystick = false;
public static boolean AppUsesAccelerometer = false;
public static boolean AppUsesGyroscope = false;
public static boolean AppUsesOrientationSensor = false;
public static boolean AppUsesMultitouch = true;
public static boolean NonBlockingSwapBuffers = false;
public static boolean ResetSdlConfigForThisVersion = false;
public static String DeleteFilesOnUpgrade = "%";
public static int AppTouchscreenKeyboardKeysAmount = 3;
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 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 FirstStartMenuOptions[] = {new SettingsMenuMisc.GyroscopeCalibration(), new SettingsMenuMisc.OptionalDownloadConfig(),};
// 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 final boolean Using_SDL_1_3 = 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 boolean SwVideoMode = true;
public static boolean NeedDepthBuffer = false;
public static boolean NeedStencilBuffer = false;
public static boolean NeedGles2 = false;
public static boolean NeedGles3 = false;
public static boolean CompatibilityHacksVideo = false;
public static boolean CompatibilityHacksForceScreenUpdateMouseClick = true;
public static boolean CompatibilityHacksStaticInit = false;
public static boolean CompatibilityHacksTextInputEmulatesHwKeyboard = true;
public static int TextInputKeyboard = 0;
public static boolean KeepAspectRatioDefaultSetting = false;
public static boolean InhibitSuspend = true;
public static boolean CreateService = true;
public static String ReadmeText = "";
public static String CommandLine = "XSDL";
public static boolean AppUsesMouse = true;
public static boolean AppNeedsTwoButtonMouse = true;
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 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 AppNeedsArrowKeys = false;
public static boolean AppNeedsTextInput = false;
public static boolean AppUsesJoystick = false;
public static boolean AppUsesSecondJoystick = false;
public static boolean AppUsesThirdJoystick = false;
public static boolean AppUsesAccelerometer = false;
public static boolean AppUsesGyroscope = false;
public static boolean AppUsesOrientationSensor = false;
public static boolean AppUsesMultitouch = true;
public static boolean NonBlockingSwapBuffers = false;
public static boolean ResetSdlConfigForThisVersion = false;
public static String DeleteFilesOnUpgrade = "%";
public static int AppTouchscreenKeyboardKeysAmount = 3;
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 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 FirstStartMenuOptions[] = {new SettingsMenuMisc.GyroscopeCalibration(), new SettingsMenuMisc.OptionalDownloadConfig(),};
// Phone-specific config, modified by user in "Change phone config" startup dialog
public static int VideoDepthBpp = 16;
public static boolean HorizontalOrientation = true;
public static boolean AutoDetectOrientation = false;
public static boolean ImmersiveMode = true;
public static boolean HideSystemMousePointer = false;
public static boolean DownloadToSdcard = true;
public static boolean PhoneHasArrowKeys = false;
public static boolean UseAccelerometerAsArrowKeys = false;
public static boolean UseTouchscreenKeyboard = true;
public static int TouchscreenKeyboardSize = 1;
public static final int TOUCHSCREEN_KEYBOARD_CUSTOM = 4;
public static int TouchscreenKeyboardDrawSize = 2;
public static int TouchscreenKeyboardTheme = 0;
public static int TouchscreenKeyboardTransparency = 2;
public static boolean FloatingScreenJoystick = false;
public static int AccelerometerSensitivity = 2;
public static int AccelerometerCenterPos = 2;
public static int AudioBufferConfig = 0;
public static boolean OptionalDataDownload[] = null;
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 LeftClickTimeout = 3;
public static int RightClickTimeout = 4;
public static int RightClickMethod = AppNeedsTwoButtonMouse ? Mouse.RIGHT_CLICK_WITH_MULTITOUCH : Mouse.RIGHT_CLICK_NONE;
public static int RightClickKey = KeyEvent.KEYCODE_MENU;
public static boolean MoveMouseWithJoystick = false;
public static int MoveMouseWithJoystickSpeed = 1;
public static int MoveMouseWithJoystickAccel = 0;
public static boolean MoveMouseWithGyroscope = true;
public static int MoveMouseWithGyroscopeSpeed = 2;
public static boolean ClickMouseWithDpad = false;
public static boolean RelativeMouseMovement = ForceRelativeMouseMode; // Laptop touchpad mode
public static boolean ForceHardwareMouse = false;
public static int RelativeMouseMovementSpeed = 2;
public static int RelativeMouseMovementAccel = 0;
public static int ShowScreenUnderFinger = Mouse.ZOOM_NONE;
public static int ClickScreenPressure = 0;
public static int ClickScreenTouchspotSize = 0;
public static boolean FingerHover = true;
public static boolean HoverJitterFilter = true;
public static boolean GenerateSubframeTouchEvents = false;
public static boolean KeepAspectRatio = KeepAspectRatioDefaultSetting;
public static boolean TvBorders = true;
public static int RemapHwKeycode[] = new int[SDL_Keys.JAVA_KEYCODE_LAST];
public static int RemapScreenKbKeycode[] = new int[6];
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}} :
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}, {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 int RemapMultitouchGestureKeycode[] = new int[4];
public static boolean MultitouchGesturesUsed[] = new boolean[4];
public static int MultitouchGestureSensitivity = 1;
public static int TouchscreenCalibration[] = new int[4];
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 HomeDir = "/data/data/io.neoterm/files/home";
public static boolean VideoLinearFilter = true;
public static boolean MultiThreadedVideo = false;
// Phone-specific config, modified by user in "Change phone config" startup dialog
public static int VideoDepthBpp = 16;
public static boolean HorizontalOrientation = true;
public static boolean AutoDetectOrientation = false;
public static boolean ImmersiveMode = true;
public static boolean HideSystemMousePointer = false;
public static boolean DownloadToSdcard = true;
public static boolean PhoneHasArrowKeys = false;
public static boolean UseAccelerometerAsArrowKeys = false;
public static boolean UseTouchscreenKeyboard = true;
public static int TouchscreenKeyboardSize = 1;
public static final int TOUCHSCREEN_KEYBOARD_CUSTOM = 4;
public static int TouchscreenKeyboardDrawSize = 2;
public static int TouchscreenKeyboardTheme = 0;
public static int TouchscreenKeyboardTransparency = 2;
public static boolean FloatingScreenJoystick = false;
public static int AccelerometerSensitivity = 2;
public static int AccelerometerCenterPos = 2;
public static int AudioBufferConfig = 0;
public static boolean OptionalDataDownload[] = null;
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 LeftClickTimeout = 3;
public static int RightClickTimeout = 4;
public static int RightClickMethod = AppNeedsTwoButtonMouse ? Mouse.RIGHT_CLICK_WITH_MULTITOUCH : Mouse.RIGHT_CLICK_NONE;
public static int RightClickKey = KeyEvent.KEYCODE_MENU;
public static boolean MoveMouseWithJoystick = false;
public static int MoveMouseWithJoystickSpeed = 1;
public static int MoveMouseWithJoystickAccel = 0;
public static boolean MoveMouseWithGyroscope = true;
public static int MoveMouseWithGyroscopeSpeed = 2;
public static boolean ClickMouseWithDpad = false;
public static boolean RelativeMouseMovement = ForceRelativeMouseMode; // Laptop touchpad mode
public static boolean ForceHardwareMouse = false;
public static int RelativeMouseMovementSpeed = 2;
public static int RelativeMouseMovementAccel = 0;
public static int ShowScreenUnderFinger = Mouse.ZOOM_NONE;
public static int ClickScreenPressure = 0;
public static int ClickScreenTouchspotSize = 0;
public static boolean FingerHover = true;
public static boolean HoverJitterFilter = true;
public static boolean GenerateSubframeTouchEvents = false;
public static boolean KeepAspectRatio = KeepAspectRatioDefaultSetting;
public static boolean TvBorders = true;
public static int RemapHwKeycode[] = new int[SDL_Keys.JAVA_KEYCODE_LAST];
public static int RemapScreenKbKeycode[] = new int[6];
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}} :
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}, {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 int RemapMultitouchGestureKeycode[] = new int[4];
public static boolean MultitouchGesturesUsed[] = new boolean[4];
public static int MultitouchGestureSensitivity = 1;
public static int TouchscreenCalibration[] = new int[4];
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 HomeDir = "/data/data/io.neoterm/files/home";
public static boolean VideoLinearFilter = true;
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 NeoAccelerometerReader(Context context) {
super(context);
}
public NeoAccelerometerReader(Context context) {
super(context);
}
public static void setGyroInvertedOrientation(boolean invertedOrientation) {
gyro.invertedOrientation = invertedOrientation;
}
public static void setGyroInvertedOrientation(boolean invertedOrientation) {
gyro.invertedOrientation = invertedOrientation;
}
}

View File

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

View File

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

View File

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

View File

@ -7,11 +7,11 @@ import io.neoterm.xorg.R;
*/
public class NeoTextInput {
public static int TextInputKeyboardList[][] =
{
{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_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}
};
public static int TextInputKeyboardList[][] =
{
{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_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}
};
}

View File

@ -7,7 +7,7 @@ import io.neoterm.xorg.NeoXorgViewClient;
*/
public class NeoXorgSettings {
public static void init(NeoXorgViewClient client) {
Settings.Load(client);
}
public static void init(NeoXorgViewClient 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 androidx.appcompat.app.AlertDialog;
import io.neoterm.xorg.R;
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
{
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;
abstract String title(final MainActivity p);
for( Menu m: list )
{
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();
}
}
boolean enabled() {
return true;
}
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)
{
Settings.settingsChanged = true;
if( Globals.OptionalDataDownload == null )
{
String downloads[] = Globals.DataDownloadUrl;
Globals.OptionalDataDownload = new boolean[downloads.length];
boolean oldFormat = true;
for( int i = 0; i < downloads.length; i++ )
{
if( downloads[i].indexOf("!") == 0 )
{
Globals.OptionalDataDownload[i] = true;
oldFormat = false;
}
}
if( oldFormat )
Globals.OptionalDataDownload[0] = true;
}
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;
if(!firstStart)
new MainMenu().run(p);
else
{
if( Globals.StartupMenuButtonTimeout > 0 ) // If we did not disable startup menu altogether
{
for( Menu m: Globals.FirstStartMenuOptions )
{
boolean hidden = false;
for( Menu m1: Globals.HiddenMenuOptions )
{
if( m1.getClass().getName().equals( m.getClass().getName() ) )
hidden = true;
}
if( ! hidden )
menuStack.add(0, m);
}
}
goBack(p);
}
}
for (Menu m : list) {
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 void goBack(final MainActivity p)
{
if(menuStack.isEmpty())
{
Settings.Save(p);
}
else
{
Menu c = menuStack.remove(menuStack.size() - 1);
c.run(p);
}
}
static ArrayList<Menu> menuStack = new ArrayList<Menu>();
static void goBackOuterMenu(final MainActivity p)
{
if(!menuStack.isEmpty())
menuStack.remove(menuStack.size() - 1);
goBack(p);
}
static class OkButton extends Menu
{
String title(final MainActivity p)
{
return p.getResources().getString(R.string.ok);
}
void run (final MainActivity p)
{
goBackOuterMenu(p);
}
}
public static void showConfig(final MainActivity p, final boolean firstStart) {
Settings.settingsChanged = true;
if (Globals.OptionalDataDownload == null) {
String downloads[] = Globals.DataDownloadUrl;
Globals.OptionalDataDownload = new boolean[downloads.length];
boolean oldFormat = true;
for (int i = 0; i < downloads.length; i++) {
if (downloads[i].indexOf("!") == 0) {
Globals.OptionalDataDownload[i] = true;
oldFormat = false;
}
}
if (oldFormat)
Globals.OptionalDataDownload[0] = true;
}
static class DummyMenu extends Menu
{
String title(final MainActivity p)
{
return p.getResources().getString(R.string.ok);
}
void run (final MainActivity p)
{
goBack(p);
}
}
if (!firstStart)
new MainMenu().run(p);
else {
if (Globals.StartupMenuButtonTimeout > 0) // If we did not disable startup menu altogether
{
for (Menu m : Globals.FirstStartMenuOptions) {
boolean hidden = false;
for (Menu m1 : Globals.HiddenMenuOptions) {
if (m1.getClass().getName().equals(m.getClass().getName()))
hidden = true;
}
if (!hidden)
menuStack.add(0, m);
}
}
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);
}
}
static void goBack(final MainActivity p) {
if (menuStack.isEmpty()) {
Settings.Save(p);
} else {
Menu c = menuStack.remove(menuStack.size() - 1);
c.run(p);
}
}
static void goBackOuterMenu(final MainActivity p) {
if (!menuStack.isEmpty())
menuStack.remove(menuStack.size() - 1);
goBack(p);
}
static class OkButton extends Menu {
String title(final MainActivity p) {
return p.getResources().getString(R.string.ok);
}
void run(final MainActivity p) {
goBackOuterMenu(p);
}
}
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;
import java.io.InputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.EOFException;
import android.util.Log;
import java.io.InputStream;
/**
* 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
* is very limited, and we're hitting memory limit on emulator.
*/
public class XZInputStream extends InputStream
{
private long nativeData = 0;
private InputStream in = null;
private final byte[] inBuf = new byte[8192];
private int inOffset = 0;
private int inAvailable = 0;
private boolean outBufEof = false;
private int offsets[] = new int[2];
public class XZInputStream extends InputStream {
private long nativeData = 0;
private InputStream in = null;
private final byte[] inBuf = new byte[8192];
private int inOffset = 0;
private int inAvailable = 0;
private boolean outBufEof = false;
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
{
this.in = in;
if (in == null)
{
throw new NullPointerException("InputStream == null");
}
nativeData = nativeInit();
if (nativeData == 0)
{
throw new OutOfMemoryError("Cannot initialize JNI liblzma object");
}
}
public XZInputStream(InputStream in) throws IOException {
this.in = in;
if (in == null) {
throw new NullPointerException("InputStream == null");
}
nativeData = nativeInit();
if (nativeData == 0) {
throw new OutOfMemoryError("Cannot initialize JNI liblzma object");
}
}
@Override
public int available() throws IOException
{
return 0; // Don't care
}
@Override
public int available() throws IOException {
return 0; // Don't care
}
@Override
public void close() throws IOException
{
synchronized (this)
{
if (nativeData != 0)
nativeClose(nativeData);
nativeData = 0;
if (in != null)
{
try {
in.close();
} finally {
in = null;
}
}
}
}
@Override
public void close() throws IOException {
synchronized (this) {
if (nativeData != 0)
nativeClose(nativeData);
nativeData = 0;
if (in != null) {
try {
in.close();
} finally {
in = null;
}
}
}
}
@Override
protected void finalize() throws IOException
{
try {
close();
} finally {
try {
super.finalize();
} catch (Throwable t) {
throw new AssertionError(t);
}
}
}
@Override
protected void finalize() throws IOException {
try {
close();
} finally {
try {
super.finalize();
} catch (Throwable t) {
throw new AssertionError(t);
}
}
}
@Override
public int read() throws IOException
{
return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
}
@Override
public int read() throws IOException {
return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
}
@Override
public int read(byte[] outBuf, int outOffset, int outCount) throws IOException
{
//Log.i("SDL", "XZInputStream.read: outOffset " + outOffset + " outCount " + outCount + " outBufEof " + outBufEof +
// " inOffset " + inOffset + " inAvailable " + inAvailable);
if (outBufEof)
return -1;
if (outCount <= 0)
return 0;
@Override
public int read(byte[] outBuf, int outOffset, int outCount) throws IOException {
//Log.i("SDL", "XZInputStream.read: outOffset " + outOffset + " outCount " + outCount + " outBufEof " + outBufEof +
// " inOffset " + inOffset + " inAvailable " + inAvailable);
if (outBufEof)
return -1;
if (outCount <= 0)
return 0;
int oldOutOffset = outOffset;
int oldOutOffset = outOffset;
if (inOffset >= inAvailable && inAvailable != -1)
{
inAvailable = in.read(inBuf, 0, inBuf.length);
inOffset = 0;
//Log.i("SDL", "XZInputStream.read: in.read: inOffset " + inOffset + " inAvailable " + inAvailable);
}
if (inOffset >= inAvailable && inAvailable != -1) {
inAvailable = in.read(inBuf, 0, inBuf.length);
inOffset = 0;
//Log.i("SDL", "XZInputStream.read: in.read: inOffset " + inOffset + " inAvailable " + inAvailable);
}
offsets[0] = inOffset;
offsets[1] = outOffset;
int ret = nativeRead(nativeData, inBuf, inAvailable, outBuf, outCount, offsets);
inOffset = offsets[0];
outOffset = offsets[1];
//Log.i("SDL", "XZInputStream.read: nativeRead: outOffset " + outOffset + " outCount " + outCount + " outBufEof " + outBufEof +
// " inOffset " + inOffset + " inAvailable " + inAvailable + " ret " + ret);
offsets[0] = inOffset;
offsets[1] = outOffset;
int ret = nativeRead(nativeData, inBuf, inAvailable, outBuf, outCount, offsets);
inOffset = offsets[0];
outOffset = offsets[1];
//Log.i("SDL", "XZInputStream.read: nativeRead: outOffset " + outOffset + " outCount " + outCount + " outBufEof " + outBufEof +
// " inOffset " + inOffset + " inAvailable " + inAvailable + " ret " + ret);
if (ret != 0)
{
if (ret == 1)
{
if (inOffset < inAvailable)
throw new IOException("Garbage at the end of LZMA stream");
if (inAvailable != -1)
inAvailable = in.read(inBuf, 0, inBuf.length);
if (inAvailable != -1)
throw new IOException("Garbage at the end of LZMA stream");
outBufEof = true;
}
else
{
throw new IOException("LZMA error " + ret);
}
}
if (ret != 0) {
if (ret == 1) {
if (inOffset < inAvailable)
throw new IOException("Garbage at the end of LZMA stream");
if (inAvailable != -1)
inAvailable = in.read(inBuf, 0, inBuf.length);
if (inAvailable != -1)
throw new IOException("Garbage at the end of LZMA stream");
outBufEof = true;
} else {
throw new IOException("LZMA error " + ret);
}
}
//Log.i("SDL", "XZInputStream.read: returning " + (outOffset - oldOutOffset));
return outOffset - oldOutOffset;
}
//Log.i("SDL", "XZInputStream.read: returning " + (outOffset - oldOutOffset));
return outOffset - oldOutOffset;
}
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 long nativeInit();
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.view.Window;
import android.view.WindowManager;
import io.neoterm.NeoGLView;
/**
@ -11,35 +10,35 @@ import io.neoterm.NeoGLView;
*/
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"?>
<resources>
<string name="init">初始化中</string>
<string name="please_wait">正在下载数据,请稍候</string>
<string name="init">初始化中</string>
<string name="please_wait">正在下载数据,请稍候</string>
<string name="device_config">设备配置</string>
<string name="device_change_cfg">更改设备配置</string>
<string name="device_config">设备配置</string>
<string name="device_change_cfg">更改设备配置</string>
<string name="download_unneeded">没有需要下载的内容</string>
<string name="connecting_to">正在连接到 %s</string>
<string name="failed_connecting_to">连接到 %s 失败</string>
<string name="error_connecting_to"> %s 连接出错</string>
<string name="dl_from">正在从 %s 下载数据</string>
<string name="error_dl_from">从 %s 下载数据时出错</string>
<string name="error_write">写入到 %s 时出错</string>
<string name="dl_progress">%1$.0f%% 已完成: 文件 %2$s</string>
<string name="dl_finished">已完成</string>
<string name="download_unneeded">没有需要下载的内容</string>
<string name="connecting_to">正在连接到 %s</string>
<string name="failed_connecting_to">连接到 %s 失败</string>
<string name="error_connecting_to">%s 连接出错</string>
<string name="dl_from">正在从 %s 下载数据</string>
<string name="error_dl_from">从 %s 下载数据时出错</string>
<string name="error_write">写入到 %s 时出错</string>
<string name="dl_progress">%1$.0f%% 已完成: 文件 %2$s</string>
<string name="dl_finished">已完成</string>
<string name="storage_phone">内部储存 - %d MB 空闲</string>
<string name="storage_sd">SD卡储存 - %d MB 空闲</string>
<string name="storage_custom">自定义目录</string>
<string name="storage_commandline">命令行参数,每行一个参数</string>
<string name="storage_question">数据文件安装位置</string>
<string name="optional_downloads">下载</string>
<string name="downloads">下载</string>
<string name="ok">完成</string>
<string name="cancel">取消</string>
<string name="storage_phone">内部储存 - %d MB 空闲</string>
<string name="storage_sd">SD卡储存 - %d MB 空闲</string>
<string name="storage_custom">自定义目录</string>
<string name="storage_commandline">命令行参数,每行一个参数</string>
<string name="storage_question">数据文件安装位置</string>
<string name="optional_downloads">下载</string>
<string name="downloads">下载</string>
<string name="ok">完成</string>
<string name="cancel">取消</string>
<string name="controls_arrows">箭头 / 操纵杆 / 方向键</string>
<string name="controls_trackball">轨迹球</string>
<string name="controls_accel">加速度计</string>
<string name="controls_touch">只使用触屏</string>
<string name="controls_question">您的设备有哪些导航键?</string>
<string name="controls_arrows">箭头 / 操纵杆 / 方向键</string>
<string name="controls_trackball">轨迹球</string>
<string name="controls_accel">加速度计</string>
<string name="controls_touch">只使用触屏</string>
<string name="controls_question">您的设备有哪些导航键?</string>
<string name="controls_additional">附加控制</string>
<string name="controls_screenkb">屏幕键盘</string>
<string name="controls_accelnav">加速度计</string>
<string name="controls_additional">附加控制</string>
<string name="controls_screenkb">屏幕键盘</string>
<string name="controls_accelnav">加速度计</string>
<string name="controls_screenkb_size">屏幕键盘大小</string>
<string name="controls_screenkb_drawsize">按钮大小</string>
<string name="controls_screenkb_large"></string>
<string name="controls_screenkb_medium"></string>
<string name="controls_screenkb_small"></string>
<string name="controls_screenkb_tiny">微小</string>
<string name="controls_screenkb_custom">自定义</string>
<string name="controls_screenkb_theme">屏幕键盘主题</string>
<string name="controls_screenkb_by">%1$s by %2$s</string>
<string name="controls_screenkb_transparency">屏幕键盘透明度</string>
<string name="controls_screenkb_trans_0">隐形</string>
<string name="controls_screenkb_trans_1">半隐形</string>
<string name="controls_screenkb_trans_2">透明</string>
<string name="controls_screenkb_trans_3">半透明</string>
<string name="controls_screenkb_trans_4">不透明</string>
<string name="controls_screenkb_size">屏幕键盘大小</string>
<string name="controls_screenkb_drawsize">按钮大小</string>
<string name="controls_screenkb_large"></string>
<string name="controls_screenkb_medium"></string>
<string name="controls_screenkb_small"></string>
<string name="controls_screenkb_tiny">微小</string>
<string name="controls_screenkb_custom">自定义</string>
<string name="controls_screenkb_theme">屏幕键盘主题</string>
<string name="controls_screenkb_by">%1$s by %2$s</string>
<string name="controls_screenkb_transparency">屏幕键盘透明度</string>
<string name="controls_screenkb_trans_0">隐形</string>
<string name="controls_screenkb_trans_1">半隐形</string>
<string name="controls_screenkb_trans_2">透明</string>
<string name="controls_screenkb_trans_3">半透明</string>
<string name="controls_screenkb_trans_4">不透明</string>
<string name="trackball_no_dampening">无阻碍</string>
<string name="trackball_fast"></string>
<string name="trackball_medium"></string>
<string name="trackball_slow"></string>
<string name="trackball_question">轨迹球阻碍</string>
<string name="trackball_no_dampening">无阻碍</string>
<string name="trackball_fast"></string>
<string name="trackball_medium"></string>
<string name="trackball_slow"></string>
<string name="trackball_question">轨迹球阻碍</string>
<string name="accel_veryfast">非常快</string>
<string name="accel_fast"></string>
<string name="accel_medium"></string>
<string name="accel_slow"></string>
<string name="accel_veryslow">非常慢</string>
<string name="accel_question">加速度计灵敏度</string>
<string name="accel_veryfast">非常快</string>
<string name="accel_fast"></string>
<string name="accel_medium"></string>
<string name="accel_slow"></string>
<string name="accel_veryslow">非常慢</string>
<string name="accel_question">加速度计灵敏度</string>
<string name="accel_floating">Floating</string>
<string name="accel_fixed_start">在应用程序启动时修复</string>
<string name="accel_fixed_horiz">Fixed to table desk orientation</string>
<string name="accel_question_center">加速度计中心位置</string>
<string name="accel_floating">Floating</string>
<string name="accel_fixed_start">在应用程序启动时修复</string>
<string name="accel_fixed_horiz">Fixed to table desk orientation</string>
<string name="accel_question_center">加速度计中心位置</string>
<string name="mouse_emulation">鼠标模拟</string>
<string name="rightclick_question">单击鼠标右键</string>
<string name="rightclick_menu">菜单键</string>
<string name="rightclick_key">物理按键</string>
<string name="rightclick_multitouch">双指触摸</string>
<string name="rightclick_pressure">使用按压力度</string>
<string name="rightclick_none">禁用鼠标右键</string>
<string name="mouse_emulation">鼠标模拟</string>
<string name="rightclick_question">单击鼠标右键</string>
<string name="rightclick_menu">菜单键</string>
<string name="rightclick_key">物理按键</string>
<string name="rightclick_multitouch">双指触摸</string>
<string name="rightclick_pressure">使用按压力度</string>
<string name="rightclick_none">禁用鼠标右键</string>
<string name="leftclick_question">鼠标左键单击</string>
<string name="leftclick_normal">正常</string>
<string name="leftclick_near_cursor">触摸靠近鼠标光标</string>
<string name="leftclick_multitouch">双指触摸</string>
<string name="leftclick_pressure">使用按压力度</string>
<string name="leftclick_dpadcenter">轨迹球点击 / 操纵杆中心</string>
<string name="leftclick_timeout">长按一个点</string>
<string name="leftclick_tap">点击</string>
<string name="leftclick_tap_or_timeout">点击或长按</string>
<string name="leftclick_timeout_time">长按超时</string>
<string name="leftclick_timeout_time_0">0.3 秒</string>
<string name="leftclick_timeout_time_1">0.5 秒</string>
<string name="leftclick_timeout_time_2">0.7 秒</string>
<string name="leftclick_timeout_time_3">1 秒</string>
<string name="leftclick_timeout_time_4">1.5 秒</string>
<string name="click_with_dpadcenter">左键点击和轨迹球点击 / 操纵杆中心</string>
<string name="leftclick_question">鼠标左键单击</string>
<string name="leftclick_normal">正常</string>
<string name="leftclick_near_cursor">触摸靠近鼠标光标</string>
<string name="leftclick_multitouch">双指触摸</string>
<string name="leftclick_pressure">使用按压力度</string>
<string name="leftclick_dpadcenter">轨迹球点击 / 操纵杆中心</string>
<string name="leftclick_timeout">长按一个点</string>
<string name="leftclick_tap">点击</string>
<string name="leftclick_tap_or_timeout">点击或长按</string>
<string name="leftclick_timeout_time">长按超时</string>
<string name="leftclick_timeout_time_0">0.3 秒</string>
<string name="leftclick_timeout_time_1">0.5 秒</string>
<string name="leftclick_timeout_time_2">0.7 秒</string>
<string name="leftclick_timeout_time_3">1 秒</string>
<string name="leftclick_timeout_time_4">1.5 秒</string>
<string name="click_with_dpadcenter">左键点击和轨迹球点击 / 操纵杆中心</string>
<string name="advanced">高级功能</string>
<string name="mouse_keepaspectratio">保持4:3的屏幕宽高比</string>
<string name="mouse_showcreenunderfinger">在单独的窗口中显示屏幕</string>
<string name="mouse_showcreenunderfinger2">屏幕放大镜</string>
<string name="mouse_joystickmouse">使用操纵杆或轨迹球移动鼠标</string>
<string name="mouse_joystickmousespeed">使用操纵杆移动鼠标时的速度</string>
<string name="mouse_joystickmouseaccel">使用操纵杆加速移动鼠标</string>
<string name="mouse_relative">鼠标相对移动(笔记本模式)</string>
<string name="mouse_relative_speed">鼠标相对移动速度</string>
<string name="mouse_relative_accel">鼠标相对移动加速</string>
<string name="mouse_hover_jitter_filter">过滤指针/手指的抖动</string>
<string name="mouse_gyroscope_mouse">用陀螺仪控制鼠标移动</string>
<string name="mouse_gyroscope_mouse_sensitivity">陀螺仪灵敏度</string>
<string name="mouse_finger_hover">手指抖动</string>
<string name="mouse_subframe_touch_events">每一帧的多点触摸事件</string>
<string name="advanced">高级功能</string>
<string name="mouse_keepaspectratio">保持4:3的屏幕宽高比</string>
<string name="mouse_showcreenunderfinger">在单独的窗口中显示屏幕</string>
<string name="mouse_showcreenunderfinger2">屏幕放大镜</string>
<string name="mouse_joystickmouse">使用操纵杆或轨迹球移动鼠标</string>
<string name="mouse_joystickmousespeed">使用操纵杆移动鼠标时的速度</string>
<string name="mouse_joystickmouseaccel">使用操纵杆加速移动鼠标</string>
<string name="mouse_relative">鼠标相对移动(笔记本模式)</string>
<string name="mouse_relative_speed">鼠标相对移动速度</string>
<string name="mouse_relative_accel">鼠标相对移动加速</string>
<string name="mouse_hover_jitter_filter">过滤指针/手指的抖动</string>
<string name="mouse_gyroscope_mouse">用陀螺仪控制鼠标移动</string>
<string name="mouse_gyroscope_mouse_sensitivity">陀螺仪灵敏度</string>
<string name="mouse_finger_hover">手指抖动</string>
<string name="mouse_subframe_touch_events">每一帧的多点触摸事件</string>
<string name="none"></string>
<string name="none"></string>
<string name="measurepressure">校准触摸屏压力</string>
<string name="measurepressure_touchplease">请将手指滑过屏幕两秒钟</string>
<string name="measurepressure_response">压力 %1$03d 半径 %2$03d</string>
<string name="measurepressure">校准触摸屏压力</string>
<string name="measurepressure_touchplease">请将手指滑过屏幕两秒钟</string>
<string name="measurepressure_response">压力 %1$03d 半径 %2$03d</string>
<string name="audiobuf_verysmall">非常小(较新的设备,延迟低)</string>
<string name="audiobuf_small"></string>
<string name="audiobuf_medium">中等</string>
<string name="audiobuf_large">大(较老的设备,如果声音不稳定请选此项)</string>
<string name="audiobuf_question">音频缓冲大小</string>
<string name="audiobuf_verysmall">非常小(较新的设备,延迟低)</string>
<string name="audiobuf_small"></string>
<string name="audiobuf_medium">中等</string>
<string name="audiobuf_large">大(较老的设备,如果声音不稳定请选此项)</string>
<string name="audiobuf_question">音频缓冲大小</string>
<string name="remap_hwkeys">映射物理按键</string>
<string name="remap_hwkeys_press">按下任意按键 除了HOME键和POWER键如音量键</string>
<string name="remap_hwkeys_select">选择SDL按键</string>
<string name="remap_hwkeys_select_simple">选择动作</string>
<string name="remap_hwkeys_select_more_keys">显示所有按键</string>
<string name="remap_hwkeys">映射物理按键</string>
<string name="remap_hwkeys_press">按下任意按键 除了HOME键和POWER键如音量键</string>
<string name="remap_hwkeys_select">选择SDL按键</string>
<string name="remap_hwkeys_select_simple">选择动作</string>
<string name="remap_hwkeys_select_more_keys">显示所有按键</string>
<string name="remap_screenkb">映射屏幕控件</string>
<string name="remap_screenkb_joystick">手柄</string>
<string name="remap_screenkb_button">按钮</string>
<string name="remap_screenkb_button_text">文本输入按钮</string>
<string name="remap_screenkb_button_gestures">双指手势</string>
<string name="remap_screenkb_button_gestures_sensitivity">双指手势灵敏度</string>
<string name="remap_screenkb_button_zoomin">双指放大</string>
<string name="remap_screenkb_button_zoomout">双指缩小</string>
<string name="remap_screenkb_button_rotateleft">双指向左旋转</string>
<string name="remap_screenkb_button_rotateright">双指向右旋转</string>
<string name="remap_screenkb">映射屏幕控件</string>
<string name="remap_screenkb_joystick">手柄</string>
<string name="remap_screenkb_button">按钮</string>
<string name="remap_screenkb_button_text">文本输入按钮</string>
<string name="remap_screenkb_button_gestures">双指手势</string>
<string name="remap_screenkb_button_gestures_sensitivity">双指手势灵敏度</string>
<string name="remap_screenkb_button_zoomin">双指放大</string>
<string name="remap_screenkb_button_zoomout">双指缩小</string>
<string name="remap_screenkb_button_rotateleft">双指向左旋转</string>
<string name="remap_screenkb_button_rotateright">双指向右旋转</string>
<string name="screenkb_custom_layout">自定义屏幕键盘布局</string>
<string name="screenkb_custom_layout_help">按返回键结束,在空白区域滑动调整按钮大小</string>
<string name="screenkb_floating_joystick">浮动操纵杆</string>
<string name="screenkb_custom_layout">自定义屏幕键盘布局</string>
<string name="screenkb_custom_layout_help">按返回键结束,在空白区域滑动调整按钮大小</string>
<string name="screenkb_floating_joystick">浮动操纵杆</string>
<string name="calibrate_touchscreen">校准触摸屏</string>
<string name="calibrate_touchscreen_touch">触摸屏幕的所有边缘,按返回键结束</string>
<string name="calibrate_touchscreen">校准触摸屏</string>
<string name="calibrate_touchscreen_touch">触摸屏幕的所有边缘,按返回键结束</string>
<string name="video">视频选项</string>
<string name="video_smooth">线性过滤</string>
<string name="video_separatethread">用单线程处理视频可能会提高FPS也可能使程序崩溃</string>
<string name="video_orientation_vertical">切换横屏/竖屏</string>
<string name="video_orientation_autodetect">自动检测屏幕方向</string>
<string name="video_bpp_24">24 bpp颜色深度</string>
<string name="video_immersive">隐藏系统导航按钮/沉浸模式</string>
<string name="tv_borders">电视边框</string>
<string name="video">视频选项</string>
<string name="video_smooth">线性过滤</string>
<string name="video_separatethread">用单线程处理视频可能会提高FPS也可能使程序崩溃</string>
<string name="video_orientation_vertical">切换横屏/竖屏</string>
<string name="video_orientation_autodetect">自动检测屏幕方向</string>
<string name="video_bpp_24">24 bpp颜色深度</string>
<string name="video_immersive">隐藏系统导航按钮/沉浸模式</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">显示仿真鼠标的大小</string>
<string name="display_size_desktop">桌面版,无仿真</string>
<string name="display_size_large">大(适用于平板电脑)</string>
<string name="display_size_small">小,放大镜</string>
<string name="display_size_small_touchpad">小,触摸模式</string>
<string name="display_size_tiny">很小</string>
<string name="display_size_tiny_touchpad">很小,触摸模式</string>
<string name="display_size_mouse">鼠标仿真模式</string>
<string name="display_size">显示仿真鼠标的大小</string>
<string name="display_size_desktop">桌面版,无仿真</string>
<string name="display_size_large">大(适用于平板电脑)</string>
<string name="display_size_small">小,放大镜</string>
<string name="display_size_small_touchpad">小,触摸模式</string>
<string name="display_size_tiny">很小</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_size">本程序需要 %1$d Mb 的RAM您的设备有 %2$d Mb</string>
<string name="ignore">忽略</string>
<string name="not_enough_ram">没有足够的 RAM</string>
<string name="not_enough_ram_size">本程序需要 %1$d Mb 的RAM您的设备有 %2$d Mb</string>
<string name="ignore">忽略</string>
<string name="calibrate_gyroscope">校准陀螺仪</string>
<string name="calibrate_gyroscope_text">将您的设备放在水平表面上</string>
<string name="calibrate_gyroscope_not_supported">您的设备没有陀螺仪</string>
<string name="calibrate_gyroscope">校准陀螺仪</string>
<string name="calibrate_gyroscope_text">将您的设备放在水平表面上</string>
<string name="calibrate_gyroscope_not_supported">您的设备没有陀螺仪</string>
<string name="reset_config">将所有配置重置为默认值</string>
<string name="reset_config_ask">是否将所有选项重置为默认值?</string>
<string name="reset_config">将所有配置重置为默认值</string>
<string name="reset_config_ask">是否将所有选项重置为默认值?</string>
<string name="cancel_download">是否取消数据下载?</string>
<string name="cancel_download_resume">您可以稍后恢复它,数据不会被下载两次。</string>
<string name="yes"></string>
<string name="no"></string>
<string name="cancel_download">是否取消数据下载?</string>
<string name="cancel_download_resume">您可以稍后恢复它,数据不会被下载两次。</string>
<string name="yes"></string>
<string name="no"></string>
<!-- Play Game Services strings -->
<string name="gamehelper_sign_in_failed">无法登录,请检查您的网络连接,然后重试。</string>
<string name="gamehelper_app_misconfigured">应用程序配置不正确。请检查包名和签名证书是否与开发者控制台中创建的客户端ID一致。此外如果应用程序尚未发布请检查您的帐户是否为测试人员帐户。详细信息请参阅日志。</string>
<string name="gamehelper_license_failed">许可证检查失败。</string>
<string name="gamehelper_unknown_error">未知错误。</string>
<string name="accessing_network">正在访问网络,请稍候</string>
<!-- Play Game Services strings -->
<string name="gamehelper_sign_in_failed">无法登录,请检查您的网络连接,然后重试。</string>
<string name="gamehelper_app_misconfigured">
应用程序配置不正确。请检查包名和签名证书是否与开发者控制台中创建的客户端ID一致。此外如果应用程序尚未发布请检查您的帐户是否为测试人员帐户。详细信息请参阅日志。
</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_stop">停止</string>
<string name="notification_app_is_running">%s 正在运行中</string>
<string name="notification_stop">停止</string>
</resources>

View File

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

View File

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

View File

@ -2,205 +2,211 @@
<resources
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingTranslation">
<string name="init">Initializing</string>
<string name="please_wait">Please wait while data is being downloaded</string>
<string name="init">Initializing</string>
<string name="please_wait">Please wait while data is being downloaded</string>
<string name="device_config">Device configuration</string>
<string name="device_change_cfg">Change device configuration</string>
<string name="device_config">Device configuration</string>
<string name="device_change_cfg">Change device configuration</string>
<string name="download_unneeded">No need to download</string>
<string name="connecting_to">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="dl_from">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="dl_progress">%1$.0f%% done: file %2$s</string>
<string name="dl_finished">Finished</string>
<string name="download_unneeded">No need to download</string>
<string name="connecting_to">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="dl_from">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="dl_progress">%1$.0f%% done: file %2$s</string>
<string name="dl_finished">Finished</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_custom">Specify directory</string>
<string name="storage_commandline">Command line parameters, one argument per line</string>
<string name="storage_question">Data installation location</string>
<string name="optional_downloads">Downloads</string>
<string name="downloads">Downloads</string>
<string name="ok">OK</string>
<string name="cancel">Cancel</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_custom">Specify directory</string>
<string name="storage_commandline">Command line parameters, one argument per line</string>
<string name="storage_question">Data installation location</string>
<string name="optional_downloads">Downloads</string>
<string name="downloads">Downloads</string>
<string name="ok">OK</string>
<string name="cancel">Cancel</string>
<string name="controls_arrows">Arrows / joystick / dpad</string>
<string name="controls_trackball">Trackball</string>
<string name="controls_accel">Accelerometer</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_arrows">Arrows / joystick / dpad</string>
<string name="controls_trackball">Trackball</string>
<string name="controls_accel">Accelerometer</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_additional">Additional controls</string>
<string name="controls_screenkb">On-screen screenKeyboard</string>
<string name="controls_accelnav">Accelerometer</string>
<string name="controls_additional">Additional controls</string>
<string name="controls_screenkb">On-screen screenKeyboard</string>
<string name="controls_accelnav">Accelerometer</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_large">Large</string>
<string name="controls_screenkb_medium">Medium</string>
<string name="controls_screenkb_small">Small</string>
<string name="controls_screenkb_tiny">Tiny</string>
<string name="controls_screenkb_custom">Custom</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_transparency">On-screen screenKeyboard transparency</string>
<string name="controls_screenkb_trans_0">Invisible</string>
<string name="controls_screenkb_trans_1">Almost invisible</string>
<string name="controls_screenkb_trans_2">Transparent</string>
<string name="controls_screenkb_trans_3">Semi-transparent</string>
<string name="controls_screenkb_trans_4">Non-transparent</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_large">Large</string>
<string name="controls_screenkb_medium">Medium</string>
<string name="controls_screenkb_small">Small</string>
<string name="controls_screenkb_tiny">Tiny</string>
<string name="controls_screenkb_custom">Custom</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_transparency">On-screen screenKeyboard transparency</string>
<string name="controls_screenkb_trans_0">Invisible</string>
<string name="controls_screenkb_trans_1">Almost invisible</string>
<string name="controls_screenkb_trans_2">Transparent</string>
<string name="controls_screenkb_trans_3">Semi-transparent</string>
<string name="controls_screenkb_trans_4">Non-transparent</string>
<string name="trackball_no_dampening">No dampening</string>
<string name="trackball_fast">Fast</string>
<string name="trackball_medium">Medium</string>
<string name="trackball_slow">Slow</string>
<string name="trackball_question">Trackball dampening</string>
<string name="trackball_no_dampening">No dampening</string>
<string name="trackball_fast">Fast</string>
<string name="trackball_medium">Medium</string>
<string name="trackball_slow">Slow</string>
<string name="trackball_question">Trackball dampening</string>
<string name="accel_veryfast">Very fast</string>
<string name="accel_fast">Fast</string>
<string name="accel_medium">Medium</string>
<string name="accel_slow">Slow</string>
<string name="accel_veryslow">Very slow</string>
<string name="accel_question">Accelerometer sensitivity</string>
<string name="accel_veryfast">Very fast</string>
<string name="accel_fast">Fast</string>
<string name="accel_medium">Medium</string>
<string name="accel_slow">Slow</string>
<string name="accel_veryslow">Very slow</string>
<string name="accel_question">Accelerometer sensitivity</string>
<string name="accel_floating">Floating</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_question_center">Accelerometer center position</string>
<string name="accel_floating">Floating</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_question_center">Accelerometer center position</string>
<string name="mouse_emulation">Mouse emulation</string>
<string name="rightclick_question">Right mouse click</string>
<string name="rightclick_menu">Menu key</string>
<string name="rightclick_key">Physical key</string>
<string name="rightclick_multitouch">Touch screen with second finger</string>
<string name="rightclick_pressure">Touch screen with force</string>
<string name="rightclick_none">Disable right mouse click</string>
<string name="mouse_emulation">Mouse emulation</string>
<string name="rightclick_question">Right mouse click</string>
<string name="rightclick_menu">Menu key</string>
<string name="rightclick_key">Physical key</string>
<string name="rightclick_multitouch">Touch screen with second finger</string>
<string name="rightclick_pressure">Touch screen with force</string>
<string name="rightclick_none">Disable right mouse click</string>
<string name="leftclick_question">Left mouse click</string>
<string name="leftclick_normal">Normal</string>
<string name="leftclick_near_cursor">Touch near mouse cursor</string>
<string name="leftclick_multitouch">Touch screen with second finger</string>
<string name="leftclick_pressure">Touch screen with force</string>
<string name="leftclick_dpadcenter">Trackball click / joystick center</string>
<string name="leftclick_timeout">Hold at the same spot</string>
<string name="leftclick_tap">Tap</string>
<string name="leftclick_tap_or_timeout">Tap or hold</string>
<string name="leftclick_timeout_time">Holding timeout</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_2">0.7 sec</string>
<string name="leftclick_timeout_time_3">1 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="leftclick_question">Left mouse click</string>
<string name="leftclick_normal">Normal</string>
<string name="leftclick_near_cursor">Touch near mouse cursor</string>
<string name="leftclick_multitouch">Touch screen with second finger</string>
<string name="leftclick_pressure">Touch screen with force</string>
<string name="leftclick_dpadcenter">Trackball click / joystick center</string>
<string name="leftclick_timeout">Hold at the same spot</string>
<string name="leftclick_tap">Tap</string>
<string name="leftclick_tap_or_timeout">Tap or hold</string>
<string name="leftclick_timeout_time">Holding timeout</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_2">0.7 sec</string>
<string name="leftclick_timeout_time_3">1 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="advanced">Advanced features</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_showcreenunderfinger2">On-screen magnifying glass</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_joystickmouseaccel">Move mouse with joystick acceleration</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_accel">Relative mouse movement acceleration</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_sensitivity">Gyroscope sensitivity</string>
<string name="mouse_finger_hover">Finger hover</string>
<string name="mouse_subframe_touch_events">Multiple touch events per video frame</string>
<string name="advanced">Advanced features</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_showcreenunderfinger2">On-screen magnifying glass</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_joystickmouseaccel">Move mouse with joystick acceleration</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_accel">Relative mouse movement acceleration</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_sensitivity">Gyroscope sensitivity</string>
<string name="mouse_finger_hover">Finger hover</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_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">Calibrate touchscreen pressure</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="audiobuf_verysmall">Very small (fast devices, less lag)</string>
<string name="audiobuf_small">Small</string>
<string name="audiobuf_medium">Medium</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_verysmall">Very small (fast devices, less lag)</string>
<string name="audiobuf_small">Small</string>
<string name="audiobuf_medium">Medium</string>
<string name="audiobuf_large">Large (older devices, if sound is choppy)</string>
<string name="audiobuf_question">Size of audio buffer</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_select">Select SDL keycode</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">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_select">Select SDL keycode</string>
<string name="remap_hwkeys_select_simple">Select action</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_joystick">Joystick</string>
<string name="remap_screenkb_button">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_sensitivity">Two-finger screen gestures sensitivity</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_rotateleft">Rotate left two-finger gesture</string>
<string name="remap_screenkb_button_rotateright">Rotate right two-finger gesture</string>
<string name="remap_screenkb">Remap on-screen controls</string>
<string name="remap_screenkb_joystick">Joystick</string>
<string name="remap_screenkb_button">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_sensitivity">Two-finger screen gestures sensitivity</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_rotateleft">Rotate left 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_help">Press BACK when done. Resize buttons by sliding on empty space.</string>
<string name="screenkb_floating_joystick">Floating joystick</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_floating_joystick">Floating joystick</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">Calibrate touchscreen</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_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_orientation_vertical">Portrait/vertical screen orientation</string>
<string name="video_orientation_autodetect">Auto-detect screen orientation</string>
<string name="video_bpp_24">24 bpp screen color depth</string>
<string name="video_immersive">Hide system navigation buttons / immersive mode</string>
<string name="tv_borders">TV borders</string>
<string name="video">Video settings</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_orientation_vertical">Portrait/vertical screen orientation</string>
<string name="video_orientation_autodetect">Auto-detect screen orientation</string>
<string name="video_bpp_24">24 bpp screen color depth</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">Display size for mouse emulation</string>
<string name="display_size_desktop">Desktop, no emulation</string>
<string name="display_size_large">Large (tablets)</string>
<string name="display_size_small">Small, magnifying glass</string>
<string name="display_size_small_touchpad">Small, touchpad mode</string>
<string name="display_size_tiny">Tiny</string>
<string name="display_size_tiny_touchpad">Tiny, touchpad 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_desktop">Desktop, no emulation</string>
<string name="display_size_large">Large (tablets)</string>
<string name="display_size_small">Small, magnifying glass</string>
<string name="display_size_small_touchpad">Small, touchpad mode</string>
<string name="display_size_tiny">Tiny</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_size">This app needs %1$d Mb RAM, your device has %2$d Mb</string>
<string name="ignore">Ignore</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="ignore">Ignore</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_not_supported">Your device does not have 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_not_supported">Your device does not have gyroscope</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">Reset config to defaults</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_resume">You can resume it later, the data will not be downloaded twice.</string>
<string name="yes">Yes</string>
<string name="no">No</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="yes">Yes</string>
<string name="no">No</string>
<!-- 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_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 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>
<!-- 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_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 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_stop">Stop</string>
<string name="notification_app_is_running">%s is running</string>
<string name="notification_stop">Stop</string>
</resources>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,53 +1,55 @@
<?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p"
android:horizontalGap="0px"
android:verticalGap="0px"
android:keyHeight="10%p">
<Row>
<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="33" android:keyLabel="E" 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="53" android:keyLabel="Y" 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="43" android:keyLabel="O" android:isRepeatable="true"/>
<Key android:codes="44" android:keyLabel="P" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<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="32" android:keyLabel="D" 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="36" android:keyLabel="H" 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="40" android:keyLabel="L" android:isRepeatable="true"/>
<Key android:codes="74" android:keyLabel=":" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<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="52" android:keyLabel="X" 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="30" android:keyLabel="B" 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="67" android:keyLabel="≪ ×" android:keyWidth="20%p" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="!@#…" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="71" android:keyLabel="{" android:isRepeatable="true"/>
<Key android:codes="72" android:keyLabel="}" android:isRepeatable="true"/>
<Key android:codes="76" android:keyLabel="\?" android:isRepeatable="true"/>
<Key android:codes="62" android:keyLabel="Space" android:keyWidth="20%p" android:isRepeatable="true"/>
<Key android:codes="55" android:keyLabel="&lt;" android:isRepeatable="true"/>
<Key android:codes="56" android:keyLabel="&gt;" android:isRepeatable="true"/>
<Key android:codes="66" android:keyLabel="Enter" android:keyWidth="20%p" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
android:keyWidth="10%p"
android:horizontalGap="0px"
android:verticalGap="0px"
android:keyHeight="10%p">
<Row>
<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="33" android:keyLabel="E" 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="53" android:keyLabel="Y" 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="43" android:keyLabel="O" android:isRepeatable="true"/>
<Key android:codes="44" android:keyLabel="P" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<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="32" android:keyLabel="D" 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="36" android:keyLabel="H" 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="40" android:keyLabel="L" android:isRepeatable="true"/>
<Key android:codes="74" android:keyLabel=":" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<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="52" android:keyLabel="X" 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="30" android:keyLabel="B" 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="67" android:keyLabel="≪ ×" android:keyWidth="20%p" android:keyEdgeFlags="right"
android:isRepeatable="true"/>
</Row>
<Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="!@#…" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="71" android:keyLabel="{" android:isRepeatable="true"/>
<Key android:codes="72" android:keyLabel="}" android:isRepeatable="true"/>
<Key android:codes="76" android:keyLabel="\?" android:isRepeatable="true"/>
<Key android:codes="62" android:keyLabel="Space" android:keyWidth="20%p" android:isRepeatable="true"/>
<Key android:codes="55" android:keyLabel="&lt;" android:isRepeatable="true"/>
<Key android:codes="56" android:keyLabel="&gt;" android:isRepeatable="true"/>
<Key android:codes="66" android:keyLabel="Enter" android:keyWidth="20%p" android:keyEdgeFlags="right"
android:isRepeatable="true"/>
</Row>
</Keyboard>

View File

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

View File

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

View File

@ -1,53 +1,55 @@
<?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p"
android:horizontalGap="0px"
android:verticalGap="0px"
android:keyHeight="10%p">
<Row>
<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="33" android:keyLabel="e" 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="53" android:keyLabel="y" 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="43" android:keyLabel="o" android:isRepeatable="true"/>
<Key android:codes="44" android:keyLabel="p" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<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="32" android:keyLabel="d" 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="36" android:keyLabel="h" 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="40" android:keyLabel="l" android:isRepeatable="true"/>
<Key android:codes="74" android:keyLabel=";" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<Key android:codes="-1" android:keyLabel="⇪" android:keyEdgeFlags="left"/>
<Key android:codes="54" android:keyLabel="z" 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="50" android:keyLabel="v" 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="41" android:keyLabel="m" android:isRepeatable="true"/>
<Key android:codes="67" android:keyLabel="≪ ×" android:keyWidth="20%p" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="123…" android:keyEdgeFlags="left"/>
<Key android:codes="71" android:keyLabel="[" android:isRepeatable="true"/>
<Key android:codes="72" android:keyLabel="]" android:isRepeatable="true"/>
<Key android:codes="76" android:keyLabel="/" android:isRepeatable="true"/>
<Key android:codes="62" android:keyLabel="Space" android:keyWidth="20%p" android:isRepeatable="true"/>
<Key android:codes="55" android:keyLabel="," android:isRepeatable="true"/>
<Key android:codes="56" android:keyLabel="." android:isRepeatable="true"/>
<Key android:codes="66" android:keyLabel="Enter" android:keyWidth="20%p" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
android:keyWidth="10%p"
android:horizontalGap="0px"
android:verticalGap="0px"
android:keyHeight="10%p">
<Row>
<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="33" android:keyLabel="e" 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="53" android:keyLabel="y" 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="43" android:keyLabel="o" android:isRepeatable="true"/>
<Key android:codes="44" android:keyLabel="p" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<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="32" android:keyLabel="d" 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="36" android:keyLabel="h" 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="40" android:keyLabel="l" android:isRepeatable="true"/>
<Key android:codes="74" android:keyLabel=";" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<Key android:codes="-1" android:keyLabel="⇪" android:keyEdgeFlags="left"/>
<Key android:codes="54" android:keyLabel="z" 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="50" android:keyLabel="v" 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="41" android:keyLabel="m" android:isRepeatable="true"/>
<Key android:codes="67" android:keyLabel="≪ ×" android:keyWidth="20%p" android:keyEdgeFlags="right"
android:isRepeatable="true"/>
</Row>
<Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="123…" android:keyEdgeFlags="left"/>
<Key android:codes="71" android:keyLabel="[" android:isRepeatable="true"/>
<Key android:codes="72" android:keyLabel="]" android:isRepeatable="true"/>
<Key android:codes="76" android:keyLabel="/" android:isRepeatable="true"/>
<Key android:codes="62" android:keyLabel="Space" android:keyWidth="20%p" android:isRepeatable="true"/>
<Key android:codes="55" android:keyLabel="," android:isRepeatable="true"/>
<Key android:codes="56" android:keyLabel="." android:isRepeatable="true"/>
<Key android:codes="66" android:keyLabel="Enter" android:keyWidth="20%p" android:keyEdgeFlags="right"
android:isRepeatable="true"/>
</Row>
</Keyboard>

View File

@ -1,57 +1,57 @@
<?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p"
android:horizontalGap="0px"
android:verticalGap="0px"
android:keyHeight="10%p">
android:keyWidth="10%p"
android:horizontalGap="0px"
android:verticalGap="0px"
android:keyHeight="10%p">
<Row>
<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="10" android:keyLabel="3" 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="13" android:keyLabel="6" 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="16" android:keyLabel="9" android:isRepeatable="true"/>
<Key android:codes="7" android:keyLabel="0" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<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="69" 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="124" android:keyLabel="Ins" 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="19" android:keyLabel="↑" android:isRepeatable="true"/>
<Key android:codes="123" android:keyLabel="End" android:isRepeatable="true" android:keyEdgeFlags="right" />
</Row>
<Row>
<Key android:codes="-1" android:keyLabel="!@#…" android:keyEdgeFlags="left"/>
<Key android:codes="75" android:keyLabel="'" 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="115" android:keyLabel="CapsLk" 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="21" 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"/>
</Row>
<Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="abc…" android:keyEdgeFlags="left"/>
<Key android:codes="59" android:keyLabel="Shift" 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="57" 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="226" android:keyLabel="Menu" android:isRepeatable="true"/>
<Key android:codes="114" android:keyLabel="Ctrl" android:isSticky="true"/>
<Key android:codes="60" android:keyLabel="Shift" android:isSticky="true"/>
</Row>
<Row>
<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="10" android:keyLabel="3" 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="13" android:keyLabel="6" 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="16" android:keyLabel="9" android:isRepeatable="true"/>
<Key android:codes="7" android:keyLabel="0" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<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="69" 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="124" android:keyLabel="Ins" 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="19" android:keyLabel="↑" android:isRepeatable="true"/>
<Key android:codes="123" android:keyLabel="End" android:isRepeatable="true" android:keyEdgeFlags="right"/>
</Row>
<Row>
<Key android:codes="-1" android:keyLabel="!@#…" android:keyEdgeFlags="left"/>
<Key android:codes="75" android:keyLabel="'" 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="115" android:keyLabel="CapsLk" 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="21" 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"/>
</Row>
<Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="abc…" android:keyEdgeFlags="left"/>
<Key android:codes="59" android:keyLabel="Shift" 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="57" 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="226" android:keyLabel="Menu" android:isRepeatable="true"/>
<Key android:codes="114" android:keyLabel="Ctrl" android:isSticky="true"/>
<Key android:codes="60" android:keyLabel="Shift" android:isSticky="true"/>
</Row>
</Keyboard>

View File

@ -1,57 +1,57 @@
<?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p"
android:horizontalGap="0px"
android:verticalGap="0px"
android:keyHeight="10%p">
android:keyWidth="10%p"
android:horizontalGap="0px"
android:verticalGap="0px"
android:keyHeight="10%p">
<Row>
<Key android:codes="100008" android:keyLabel="!" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="100009" 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="100012" 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="100015" 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"/>
</Row>
<Row>
<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="100069" 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="100075" android:keyLabel="&quot;" 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="133" android:keyLabel="F3" android:isRepeatable="true"/>
<Key android:codes="134" android:keyLabel="F4" android:isRepeatable="true" android:keyEdgeFlags="right" />
</Row>
<Row>
<Key android:codes="-1" android:keyLabel="123…" android:keyEdgeFlags="left"/>
<Key android:codes="143" android:keyLabel="NumLk" android:isSticky="true"/>
<Key android:codes="120" android:keyLabel="Print" 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="158" android:keyLabel="Kp ." 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="137" android:keyLabel="F7" android:isRepeatable="true"/>
<Key android:codes="138" android:keyLabel="F8" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="abc…" android:keyEdgeFlags="left"/>
<Key android:codes="154" 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="157" 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="140" android:keyLabel="F10" 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"/>
</Row>
<Row>
<Key android:codes="100008" android:keyLabel="!" android:keyEdgeFlags="left" android:isRepeatable="true"/>
<Key android:codes="100009" 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="100012" 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="100015" 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"/>
</Row>
<Row>
<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="100069" 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="100075" android:keyLabel="&quot;" 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="133" android:keyLabel="F3" android:isRepeatable="true"/>
<Key android:codes="134" android:keyLabel="F4" android:isRepeatable="true" android:keyEdgeFlags="right"/>
</Row>
<Row>
<Key android:codes="-1" android:keyLabel="123…" android:keyEdgeFlags="left"/>
<Key android:codes="143" android:keyLabel="NumLk" android:isSticky="true"/>
<Key android:codes="120" android:keyLabel="Print" 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="158" android:keyLabel="Kp ." 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="137" android:keyLabel="F7" android:isRepeatable="true"/>
<Key android:codes="138" android:keyLabel="F8" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="abc…" android:keyEdgeFlags="left"/>
<Key android:codes="154" 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="157" 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="140" android:keyLabel="F10" 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"/>
</Row>
</Keyboard>

View File

@ -1,53 +1,55 @@
<?xml version="1.0"?>
<!-- When creating screenKeyboardoard layout, add it to TextInputKeyboardList array in project/java/MainActivity.java -->
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="10%p"
android:horizontalGap="0px"
android:verticalGap="0px"
android:keyHeight="10%p">
<Row>
<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="33" android:keyLabel="E" 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="53" android:keyLabel="Y" 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="43" android:keyLabel="O" android:isRepeatable="true"/>
<Key android:codes="44" android:keyLabel="P" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<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="32" android:keyLabel="D" 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="36" android:keyLabel="H" 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="40" android:keyLabel="L" android:isRepeatable="true"/>
<Key android:codes="74" android:keyLabel=":" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<Key android:codes="-1" android:keyLabel="⇫" android:keyEdgeFlags="left"/>
<Key android:codes="54" android:keyLabel="Z" 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="50" android:keyLabel="V" 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="41" android:keyLabel="M" android:isRepeatable="true"/>
<Key android:codes="67" android:keyLabel="≪ ×" android:keyWidth="20%p" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="!@#…" android:keyEdgeFlags="left"/>
<Key android:codes="71" android:keyLabel="{" android:isRepeatable="true"/>
<Key android:codes="72" android:keyLabel="}" android:isRepeatable="true"/>
<Key android:codes="76" android:keyLabel="\?" android:isRepeatable="true"/>
<Key android:codes="62" android:keyLabel="Space" android:keyWidth="20%p" android:isRepeatable="true"/>
<Key android:codes="55" android:keyLabel="&lt;" android:isRepeatable="true"/>
<Key android:codes="56" android:keyLabel="&gt;" android:isRepeatable="true"/>
<Key android:codes="66" android:keyLabel="Enter" android:keyWidth="20%p" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
android:keyWidth="10%p"
android:horizontalGap="0px"
android:verticalGap="0px"
android:keyHeight="10%p">
<Row>
<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="33" android:keyLabel="E" 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="53" android:keyLabel="Y" 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="43" android:keyLabel="O" android:isRepeatable="true"/>
<Key android:codes="44" android:keyLabel="P" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<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="32" android:keyLabel="D" 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="36" android:keyLabel="H" 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="40" android:keyLabel="L" android:isRepeatable="true"/>
<Key android:codes="74" android:keyLabel=":" android:keyEdgeFlags="right" android:isRepeatable="true"/>
</Row>
<Row>
<Key android:codes="-1" android:keyLabel="⇫" android:keyEdgeFlags="left"/>
<Key android:codes="54" android:keyLabel="Z" 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="50" android:keyLabel="V" 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="41" android:keyLabel="M" android:isRepeatable="true"/>
<Key android:codes="67" android:keyLabel="≪ ×" android:keyWidth="20%p" android:keyEdgeFlags="right"
android:isRepeatable="true"/>
</Row>
<Row android:rowEdgeFlags="bottom">
<Key android:codes="-6" android:keyLabel="!@#…" android:keyEdgeFlags="left"/>
<Key android:codes="71" android:keyLabel="{" android:isRepeatable="true"/>
<Key android:codes="72" android:keyLabel="}" android:isRepeatable="true"/>
<Key android:codes="76" android:keyLabel="\?" android:isRepeatable="true"/>
<Key android:codes="62" android:keyLabel="Space" android:keyWidth="20%p" android:isRepeatable="true"/>
<Key android:codes="55" android:keyLabel="&lt;" android:isRepeatable="true"/>
<Key android:codes="56" android:keyLabel="&gt;" android:isRepeatable="true"/>
<Key android:codes="66" android:keyLabel="Enter" android:keyWidth="20%p" android:keyEdgeFlags="right"
android:isRepeatable="true"/>
</Row>
</Keyboard>

View File

@ -2,7 +2,7 @@ package io.neoterm;
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).
@ -10,8 +10,8 @@ import static org.junit.Assert.*;
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

View File

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

View File

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

View File

@ -17,60 +17,60 @@ import io.neoterm.utils.CrashHandler
* @author kiva
*/
class App : Application() {
override fun onCreate() {
super.onCreate()
app = this
NeoPreference.init(this)
CrashHandler.init()
NeoInitializer.init(this)
override fun onCreate() {
super.onCreate()
app = this
NeoPreference.init(this)
CrashHandler.init()
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)?) {
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))
}
}
companion object {
private var app: App? = null
fun get(): App {
return app!!
}
companion object {
private var app: App? = null
fun get(): App {
return app!!
}
}
}

View File

@ -1,108 +1,110 @@
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 {
private final byte[] mBuffer;
private int mHead;
private int mStoredBytes;
private boolean mOpen = true;
private final byte[] mBuffer;
private int mHead;
private int mStoredBytes;
private boolean mOpen = true;
public ByteQueue(int size) {
mBuffer = new byte[size];
public ByteQueue(int 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() {
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");
}
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;
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;
}
}

View File

@ -4,7 +4,9 @@ import android.util.Log;
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 {
static {
System.loadLibrary("neoterm");
}
static {
System.loadLibrary("neoterm");
}
/**
* Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the
* subprocess.
* <p/>
* Callers are responsible for calling {@link #close(int)} on the returned file descriptor.
*
* @param cmd The command to execute
* @param cwd The current working directory for the executed 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 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
* 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);
/**
* Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the
* subprocess.
* <p/>
* Callers are responsible for calling {@link #close(int)} on the returned file descriptor.
*
* @param cmd The command to execute
* @param cwd The current working directory for the executed 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 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
* 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);
/** 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.
*
* @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);
/**
* 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.
*/
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.Map;
import static android.view.KeyEvent.KEYCODE_BACK;
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;
import static android.view.KeyEvent.*;
public final class KeyHandler {
public static final int KEYMOD_ALT = 0x80000000;
public static final int KEYMOD_CTRL = 0x40000000;
public static final int KEYMOD_SHIFT = 0x20000000;
public static final int KEYMOD_ALT = 0x80000000;
public static final int KEYMOD_CTRL = 0x40000000;
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 {
// terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.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("#2", KEYMOD_SHIFT | KEYCODE_MOVE_HOME); // Shifted home
TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT);
TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key
static {
// terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.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("#2", KEYMOD_SHIFT | KEYCODE_MOVE_HOME); // Shifted home
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("k1", KEYCODE_F1);
TERMCAP_TO_KEYCODE.put("k2", KEYCODE_F2);
TERMCAP_TO_KEYCODE.put("k3", KEYCODE_F3);
TERMCAP_TO_KEYCODE.put("k4", KEYCODE_F4);
TERMCAP_TO_KEYCODE.put("k5", KEYCODE_F5);
TERMCAP_TO_KEYCODE.put("k6", KEYCODE_F6);
TERMCAP_TO_KEYCODE.put("k7", KEYCODE_F7);
TERMCAP_TO_KEYCODE.put("k8", KEYCODE_F8);
TERMCAP_TO_KEYCODE.put("k9", KEYCODE_F9);
TERMCAP_TO_KEYCODE.put("k;", KEYCODE_F10);
TERMCAP_TO_KEYCODE.put("F1", KEYCODE_F11);
TERMCAP_TO_KEYCODE.put("F2", KEYCODE_F12);
TERMCAP_TO_KEYCODE.put("F3", KEYMOD_SHIFT | KEYCODE_F1);
TERMCAP_TO_KEYCODE.put("F4", KEYMOD_SHIFT | KEYCODE_F2);
TERMCAP_TO_KEYCODE.put("F5", KEYMOD_SHIFT | KEYCODE_F3);
TERMCAP_TO_KEYCODE.put("F6", KEYMOD_SHIFT | KEYCODE_F4);
TERMCAP_TO_KEYCODE.put("F7", KEYMOD_SHIFT | KEYCODE_F5);
TERMCAP_TO_KEYCODE.put("F8", KEYMOD_SHIFT | KEYCODE_F6);
TERMCAP_TO_KEYCODE.put("F9", KEYMOD_SHIFT | KEYCODE_F7);
TERMCAP_TO_KEYCODE.put("FA", KEYMOD_SHIFT | KEYCODE_F8);
TERMCAP_TO_KEYCODE.put("FB", KEYMOD_SHIFT | KEYCODE_F9);
TERMCAP_TO_KEYCODE.put("FC", KEYMOD_SHIFT | KEYCODE_F10);
TERMCAP_TO_KEYCODE.put("FD", KEYMOD_SHIFT | KEYCODE_F11);
TERMCAP_TO_KEYCODE.put("FE", KEYMOD_SHIFT | KEYCODE_F12);
TERMCAP_TO_KEYCODE.put("k1", KEYCODE_F1);
TERMCAP_TO_KEYCODE.put("k2", KEYCODE_F2);
TERMCAP_TO_KEYCODE.put("k3", KEYCODE_F3);
TERMCAP_TO_KEYCODE.put("k4", KEYCODE_F4);
TERMCAP_TO_KEYCODE.put("k5", KEYCODE_F5);
TERMCAP_TO_KEYCODE.put("k6", KEYCODE_F6);
TERMCAP_TO_KEYCODE.put("k7", KEYCODE_F7);
TERMCAP_TO_KEYCODE.put("k8", KEYCODE_F8);
TERMCAP_TO_KEYCODE.put("k9", KEYCODE_F9);
TERMCAP_TO_KEYCODE.put("k;", KEYCODE_F10);
TERMCAP_TO_KEYCODE.put("F1", KEYCODE_F11);
TERMCAP_TO_KEYCODE.put("F2", KEYCODE_F12);
TERMCAP_TO_KEYCODE.put("F3", KEYMOD_SHIFT | KEYCODE_F1);
TERMCAP_TO_KEYCODE.put("F4", KEYMOD_SHIFT | KEYCODE_F2);
TERMCAP_TO_KEYCODE.put("F5", KEYMOD_SHIFT | KEYCODE_F3);
TERMCAP_TO_KEYCODE.put("F6", KEYMOD_SHIFT | KEYCODE_F4);
TERMCAP_TO_KEYCODE.put("F7", KEYMOD_SHIFT | KEYCODE_F5);
TERMCAP_TO_KEYCODE.put("F8", KEYMOD_SHIFT | KEYCODE_F6);
TERMCAP_TO_KEYCODE.put("F9", KEYMOD_SHIFT | KEYCODE_F7);
TERMCAP_TO_KEYCODE.put("FA", KEYMOD_SHIFT | KEYCODE_F8);
TERMCAP_TO_KEYCODE.put("FB", KEYMOD_SHIFT | KEYCODE_F9);
TERMCAP_TO_KEYCODE.put("FC", KEYMOD_SHIFT | KEYCODE_F10);
TERMCAP_TO_KEYCODE.put("FD", KEYMOD_SHIFT | KEYCODE_F11);
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("kh", KEYCODE_MOVE_HOME);
TERMCAP_TO_KEYCODE.put("kl", KEYCODE_DPAD_LEFT);
TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT);
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("kl", KEYCODE_DPAD_LEFT);
TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT);
// K1=Upper left of keypad:
// t_K1 <kHome> keypad home key
// t_K3 <kPageUp> keypad page-up key
// t_K4 <kEnd> keypad end key
// t_K5 <kPageDown> keypad page-down key
TERMCAP_TO_KEYCODE.put("K1", KEYCODE_MOVE_HOME);
TERMCAP_TO_KEYCODE.put("K3", KEYCODE_PAGE_UP);
TERMCAP_TO_KEYCODE.put("K4", KEYCODE_MOVE_END);
TERMCAP_TO_KEYCODE.put("K5", KEYCODE_PAGE_DOWN);
// K1=Upper left of keypad:
// t_K1 <kHome> keypad home key
// t_K3 <kPageUp> keypad page-up key
// t_K4 <kEnd> keypad end key
// t_K5 <kPageDown> keypad page-down key
TERMCAP_TO_KEYCODE.put("K1", KEYCODE_MOVE_HOME);
TERMCAP_TO_KEYCODE.put("K3", KEYCODE_PAGE_UP);
TERMCAP_TO_KEYCODE.put("K4", KEYCODE_MOVE_END);
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("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("kF", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // terminfo=kind, scroll-forward key
TERMCAP_TO_KEYCODE.put("kI", KEYCODE_INSERT);
TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_UP);
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("kUP", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // non-standard shifted up
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("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("kI", KEYCODE_INSERT);
TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_UP);
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("kUP", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // non-standard shifted up
TERMCAP_TO_KEYCODE.put("@7", KEYCODE_MOVE_END);
TERMCAP_TO_KEYCODE.put("@8", KEYCODE_NUMPAD_ENTER);
TERMCAP_TO_KEYCODE.put("@7", KEYCODE_MOVE_END);
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) {
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') : "=";
}
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 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;
}
}

View File

@ -8,418 +8,428 @@ package io.neoterm.backend;
*/
public final class TerminalBuffer {
TerminalRow[] mLines;
/** The length of {@link #mLines}. */
int mTotalRows;
/** The number of rows and columns visible on the screen. */
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;
TerminalRow[] mLines;
/**
* The length of {@link #mLines}.
*/
int mTotalRows;
/**
* The number of rows and columns visible on the screen.
*/
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.
*
* @param columns the width of the screen in characters.
* @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
* the top of the screen.
*/
public TerminalBuffer(int columns, int totalRows, int screenRows) {
mColumns = columns;
mTotalRows = totalRows;
mScreenRows = screenRows;
mLines = new TerminalRow[totalRows];
/**
* Create a transcript screen.
*
* @param columns the width of the screen in characters.
* @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
* the top of the screen.
*/
public TerminalBuffer(int columns, int totalRows, int screenRows) {
mColumns = columns;
mTotalRows = totalRows;
mScreenRows = screenRows;
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() {
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim();
}
public int getActiveTranscriptRows() {
return mActiveTranscriptRows;
}
public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
final StringBuilder builder = new StringBuilder();
final int columns = mColumns;
public int getActiveRows() {
return mActiveTranscriptRows + mScreenRows;
}
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++) {
int x1 = (row == selY1) ? selX1 : 0;
int x2;
if (row == selY2) {
x2 = selX2 + 1;
if (x2 > columns) x2 = columns;
public void setLineWrap(int row) {
mLines[externalToInternalRow(row)].mLineWrap = true;
}
public boolean getLineWrap(int row) {
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 {
x2 = columns;
currentOutputExternalRow++;
}
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;
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 {
for (i = x1Index; i < x2Index; ++i) {
char c = line[i];
if (c != ' ') lastPrintingCharIndex = i;
}
currentOutputExternalRow++;
}
if (lastPrintingCharIndex != -1)
builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n');
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;
}
}
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() {
return mActiveTranscriptRows;
}
// Handle cursorColor scrolling off screen:
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;
/**
* 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);
}
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;
}
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) {
return mLines[externalToInternalRow(row)].mLineWrap;
}
// 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);
public void clearLineWrap(int row) {
mLines[externalToInternalRow(row)].mLineWrap = false;
}
// 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++;
/**
* 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;
// 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 {
// 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 {
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);
}
effect &= ~bits;
}
line.mStyle[x] = TextStyle.encode(foreColor, backColor, effect);
}
}
}
}

View File

@ -11,110 +11,112 @@ import java.util.Properties;
*/
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 = {
// 16 original colors. First 8 are dim.
0xff000000, // black
0xffcd0000, // dim red
0xff00cd00, // dim green
0xffcdcd00, // dim yellow
0xff6495ed, // dim blue
0xffcd00cd, // dim magenta
0xff00cdcd, // dim cyan
0xffe5e5e5, // dim white
// Second 8 are bright:
0xff7f7f7f, // medium grey
0xffff0000, // bright red
0xff00ff00, // bright green
0xffffff00, // bright yellow
0xff5c5cff, // light blue
0xffff00ff, // bright magenta
0xff00ffff, // bright cyan
0xffffffff, // bright white
/**
* http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg, but with blue color brighter.
*/
public static final int[] DEFAULT_COLORS = {
// 16 original colors. First 8 are dim.
0xff000000, // black
0xffcd0000, // dim red
0xff00cd00, // dim green
0xffcdcd00, // dim yellow
0xff6495ed, // dim blue
0xffcd00cd, // dim magenta
0xff00cdcd, // dim cyan
0xffe5e5e5, // dim white
// Second 8 are bright:
0xff7f7f7f, // medium grey
0xffff0000, // bright red
0xff00ff00, // bright green
0xffffff00, // bright yellow
0xff5c5cff, // light blue
0xffff00ff, // bright magenta
0xff00ffff, // bright cyan
0xffffffff, // bright white
// 216 color cube, six shades of each color:
0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, 0xff005fd7, 0xff005fff,
0xff008700, 0xff00875f, 0xff008787, 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff,
0xff00d700, 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, 0xff00ffff,
0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff,
0xff5f8700, 0xff5f875f, 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff,
0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, 0xff5fffd7, 0xff5fffff,
0xff870000, 0xff87005f, 0xff870087, 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff,
0xff878700, 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, 0xff87afff,
0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, 0xff87ffaf, 0xff87ffd7, 0xff87ffff,
0xffaf0000, 0xffaf005f, 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff,
0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, 0xffafafd7, 0xffafafff,
0xffafd700, 0xffafd75f, 0xffafd787, 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff,
0xffd70000, 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, 0xffd75fff,
0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, 0xffd7afaf, 0xffd7afd7, 0xffd7afff,
0xffd7d700, 0xffd7d75f, 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff,
0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, 0xffff5fd7, 0xffff5fff,
0xffff8700, 0xffff875f, 0xffff8787, 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff,
0xffffd700, 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, 0xffffffff,
// 216 color cube, six shades of each color:
0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, 0xff005fd7, 0xff005fff,
0xff008700, 0xff00875f, 0xff008787, 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff,
0xff00d700, 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, 0xff00ffff,
0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff,
0xff5f8700, 0xff5f875f, 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff,
0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, 0xff5fffd7, 0xff5fffff,
0xff870000, 0xff87005f, 0xff870087, 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff,
0xff878700, 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, 0xff87afff,
0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, 0xff87ffaf, 0xff87ffd7, 0xff87ffff,
0xffaf0000, 0xffaf005f, 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff,
0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, 0xffafafd7, 0xffafafff,
0xffafd700, 0xffafd75f, 0xffafd787, 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff,
0xffd70000, 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, 0xffd75fff,
0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, 0xffd7afaf, 0xffd7afd7, 0xffd7afff,
0xffd7d700, 0xffd7d75f, 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff,
0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, 0xffff5fd7, 0xffff5fff,
0xffff8700, 0xffff875f, 0xffff8787, 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff,
0xffffd700, 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, 0xffffffff,
// 24 grey scale ramp:
0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff626262, 0xff6c6c6c, 0xff767676,
0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee,
// 24 grey scale ramp:
0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff626262, 0xff6c6c6c, 0xff767676,
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:
0xffffffff, 0xff000000, 0xffA9AAA9};
// COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR:
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() {
reset();
public TerminalColorScheme() {
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);
}
private void reset() {
System.arraycopy(DEFAULT_COLORS, 0, mDefaultColors, 0, TextStyle.NUM_INDEXED_COLORS);
if (background != null) {
prop.put("background", background);
}
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) {
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);
if (cursor != null) {
prop.put("cursor", cursor);
}
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;
}
for (int i : color.keySet()) {
prop.put("color" + i, color.get(i));
}
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;
/** Current terminal colors (if different from default). */
/**
* Current terminal colors (if different from default).
*/
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
* 4 control sequence.
*/
public final int[] mCurrentColors = new int[TextStyle.NUM_INDEXED_COLORS];
/**
* The current terminal colors, which are normally set from the color theme, but may be set dynamically with the OSC
* 4 control sequence.
*/
public final int[] mCurrentColors = new int[TextStyle.NUM_INDEXED_COLORS];
/** Create a new instance with default colors from the theme. */
public TerminalColors() {
reset();
/**
* Create a new instance with default colors from the theme.
*/
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) {
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;
}
}
/** 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;
}
/**
* 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;
/** 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 {
/** Write a string using the UTF-8 encoding to the terminal client. */
public final void write(String data) {
byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
write(bytes, 0, bytes.length);
}
/**
* Write a string using the UTF-8 encoding to the terminal client.
*/
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 {
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 text filling this terminal row. */
public char[] mText;
/** The number of java char:s used in {@link #mText}. */
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;
/**
* The number of columns in this terminal row.
*/
private final int mColumns;
/**
* The text filling this terminal row.
*/
public char[] mText;
/**
* The number of java char:s used in {@link #mText}.
*/
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) {
mColumns = columns;
mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)];
mStyle = new long[columns];
clear(style);
/**
* Construct a blank row (containing only whitespace, ' ') with a specified style.
*/
public TerminalRow(int columns, long style) {
mColumns = columns;
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 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));
}
}
public int getSpaceUsed() {
return mSpaceUsed;
}
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. */
public int findStartOfColumn(int column) {
if (column == mColumns) return getSpaceUsed();
int currentColumn = 0;
int currentCharIndex = 0;
while (true) { // 0<2 1 < 2
int newCharIndex = currentCharIndex;
char c = mText[newCharIndex++]; // cci=1, cci=2
boolean isHigh = Character.isHighSurrogate(c);
int codePoint = isHigh ? Character.toCodePoint(c, mText[newCharIndex++]) : c;
int wcwidth = WcWidth.width(codePoint); // 1, 2
if (wcwidth > 0) {
currentColumn += wcwidth;
if (currentColumn == column) {
while (newCharIndex < mSpaceUsed) {
// Skip combining chars.
if (Character.isHighSurrogate(mText[newCharIndex])) {
if (WcWidth.width(Character.toCodePoint(mText[newCharIndex], mText[newCharIndex + 1])) <= 0) {
newCharIndex += 2;
} else {
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;
int currentColumn = 0;
int currentCharIndex = 0;
while (true) { // 0<2 1 < 2
int newCharIndex = currentCharIndex;
char c = mText[newCharIndex++]; // cci=1, cci=2
boolean isHigh = Character.isHighSurrogate(c);
int codePoint = isHigh ? Character.toCodePoint(c, mText[newCharIndex++]) : c;
int wcwidth = WcWidth.width(codePoint); // 1, 2
if (wcwidth > 0) {
currentColumn += wcwidth;
if (currentColumn == column) {
while (newCharIndex < mSpaceUsed) {
// Skip combining chars.
if (Character.isHighSurrogate(mText[newCharIndex])) {
if (WcWidth.width(Character.toCodePoint(mText[newCharIndex], mText[newCharIndex + 1])) <= 0) {
newCharIndex += 2;
} else {
break;
}
} else if (WcWidth.width(mText[newCharIndex]) <= 0) {
newCharIndex++;
} else {
mText[columnToSet] = (char) codePoint;
return;
break;
}
}
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) {
// 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);
}
// https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26
public void setChar(int columnToSet, int codePoint, long style) {
mStyle[columnToSet] = style;
char[] text = mText;
final int oldStartOfColumnIndex = findStartOfColumn(columnToSet);
final int oldCodePointDisplayWidth = WcWidth.width(text, oldStartOfColumnIndex);
final int newCodePointDisplayWidth = WcWidth.width(codePoint);
// 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;
}
}
// 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 {
mText[columnToSet] = (char) codePoint;
return;
}
}
boolean isBlank() {
for (int charIndex = 0, charLen = getSpaceUsed(); charIndex < charLen; charIndex++)
if (mText[charIndex] != ' ') return false;
return true;
final boolean newIsCombining = newCodePointDisplayWidth <= 0;
boolean wasExtraColForWideChar = (columnToSet > 0) && wideDisplayCharacterStartingAt(columnToSet - 1);
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) {
return mStyle[column];
char[] text = mText;
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.util.Log;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
@ -30,322 +26,352 @@ import java.util.UUID;
*/
public class TerminalSession extends TerminalOutput {
/** Callback to be invoked when a {@link TerminalSession} changes. */
public interface SessionChangedCallback {
void onTextChanged(TerminalSession changedSession);
/**
* Callback to be invoked when a {@link TerminalSession} changes.
*/
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 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;
}
private static final int MSG_NEW_INPUT = 1;
private static final int MSG_PROCESS_EXITED = 4;
private static final int MSG_NEW_INPUT = 1;
private static final int MSG_PROCESS_EXITED = 4;
public final String mHandle = UUID.randomUUID().toString();
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];
/**
* 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() {
return mChangeCallback;
}
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
* {@link JNI#createSubprocess(String, String, String[], String[], int[], int, int)}.
*/
private int mTerminalFileDescriptor;
/**
* Set by the application for user identification of session, not by terminal.
*/
public String mSessionName;
/** Set by the application for user identification of session, not by terminal. */
public String mSessionName;
@SuppressLint("HandlerLeak")
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
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);
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);
int bufferPosition = 0;
if (prependEscape) mUtf8InputBuffer[bufferPosition++] = 27;
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();
String exitDescription = getExitDescription(exitCode);
byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
mEmulator.append(bytesToWrite, bytesToWrite.length);
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());
}
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();
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) + ")";
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.
}
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. */
private void cleanupResources(int exitStatus) {
synchronized (this) {
mShellPid = -1;
mShellExitStatus = exitStatus;
}
int bufferPosition = 0;
if (prependEscape) mUtf8InputBuffer[bufferPosition++] = 27;
// Stop the reader and writer threads, and close the I/O streams
mTerminalToProcessIOQueue.close();
mProcessToTerminalIOQueue.close();
JNI.close(mTerminalFileDescriptor);
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();
}
/**
* 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
public void titleChanged(String oldTitle, String newTitle) {
mChangeCallback.onTitleChanged(this);
}
// Stop the reader and writer threads, and close the I/O streams
mTerminalToProcessIOQueue.close();
mProcessToTerminalIOQueue.close();
JNI.close(mTerminalFileDescriptor);
}
public synchronized boolean isRunning() {
return mShellPid != -1;
}
@Override
public void titleChanged(String oldTitle, String newTitle) {
mChangeCallback.onTitleChanged(this);
}
/** Only valid if not {@link #isRunning()}. */
public synchronized int getExitStatus() {
return mShellExitStatus;
}
public synchronized boolean isRunning() {
return mShellPid != -1;
}
@Override
public void clipboardText(String text) {
mChangeCallback.onClipboardText(this, text);
}
/**
* Only valid if not {@link #isRunning()}.
*/
public synchronized int getExitStatus() {
return mShellExitStatus;
}
@Override
public void onBell() {
mChangeCallback.onBell(this);
}
@Override
public void clipboardText(String text) {
mChangeCallback.onClipboardText(this, text);
}
@Override
public void onColorsChanged() {
mChangeCallback.onColorsChanged(this);
}
@Override
public void onBell() {
mChangeCallback.onBell(this);
}
public int getPid() {
return mShellPid;
}
@Override
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 static int CHARACTER_ATTRIBUTE_BOLD = 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_BLINK = 1 << 3;
public final static int CHARACTER_ATTRIBUTE_INVERSE = 1 << 4;
public final static int CHARACTER_ATTRIBUTE_INVISIBLE = 1 << 5;
public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6;
/**
* The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable.
* <p>
* 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.
* </p>
*/
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;
/** 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 CHARACTER_ATTRIBUTE_BOLD = 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_BLINK = 1 << 3;
public final static int CHARACTER_ATTRIBUTE_INVERSE = 1 << 4;
public final static int CHARACTER_ATTRIBUTE_INVISIBLE = 1 << 5;
public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6;
/**
* The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable.
* <p>
* 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.
* </p>
*/
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;
/**
* 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_BACKGROUND = 257;
public final static int COLOR_INDEX_CURSOR = 258;
public final static int COLOR_INDEX_FOREGROUND = 256;
public final static int COLOR_INDEX_BACKGROUND = 257;
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) {
long result = effect & 0b111111111;
if ((0xff000000 & foreColor) == 0xff000000) {
// 24-bit color.
result |= CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND | ((foreColor & 0x00ffffffL) << 40L);
} else {
// Indexed color.
result |= (foreColor & 0b111111111L) << 40;
}
if ((0xff000000 & backColor) == 0xff000000) {
// 24-bit color.
result |= CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND | ((backColor & 0x00ffffffL) << 16L);
} else {
// Indexed color.
result |= (backColor & 0b111111111L) << 16L;
}
return result;
static long encode(int foreColor, int backColor, int effect) {
long result = effect & 0b111111111;
if ((0xff000000 & foreColor) == 0xff000000) {
// 24-bit color.
result |= CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND | ((foreColor & 0x00ffffffL) << 40L);
} else {
// Indexed color.
result |= (foreColor & 0b111111111L) << 40;
}
if ((0xff000000 & backColor) == 0xff000000) {
// 24-bit color.
result |= CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND | ((backColor & 0x00ffffffL) << 16L);
} else {
// Indexed color.
result |= (backColor & 0b111111111L) << 16L;
}
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);
}
return result;
}
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) {
return (int) (style & 0b11111111111);
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) {
return (int) (style & 0b11111111111);
}
}

View File

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

View File

@ -8,18 +8,18 @@ import io.neoterm.frontend.component.NeoComponent
* @author kiva
*/
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 {
val parameter = CodeGenParameter()
return codeObject.getCodeGenerator(parameter)
}
fun newGenerator(codeObject: CodeGenObject): CodeGenerator {
val parameter = CodeGenParameter()
return codeObject.getCodeGenerator(parameter)
}
}

View File

@ -11,50 +11,54 @@ import io.neoterm.frontend.component.ComponentManager
* @author kiva
*/
class NeoColorGenerator(parameter: CodeGenParameter) : CodeGenerator(parameter) {
override fun getGeneratorName(): String {
return "NeoColorScheme-Generator"
override fun getGeneratorName(): String {
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 {
if (codeGenObject !is NeoColorScheme) {
throw RuntimeException("Invalid object type, expected NeoColorScheme, got ${codeGenObject.javaClass.simpleName}")
}
return buildString {
start(this)
generateMetaData(this, codeGenObject)
generateColors(this, codeGenObject)
end(this)
}
}
return buildString {
start(this)
generateMetaData(this, codeGenObject)
generateColors(this, codeGenObject)
end(this)
}
private fun start(builder: StringBuilder) {
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")
}
private fun start(builder: StringBuilder) {
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")
}
builder.append(" }\n")
}
}

View File

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

View File

@ -6,5 +6,5 @@ import io.neoterm.component.codegen.CodeGenParameter
* @author kiva
*/
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
*/
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
*/
class ColorSchemeComponent : ConfigFileBasedComponent<NeoColorScheme>(NeoTermPath.COLORS_PATH) {
companion object {
fun colorFile(colorName: String): File {
return File("${NeoTermPath.COLORS_PATH}/$colorName.nl")
}
companion object {
fun colorFile(colorName: String): File {
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
get() = true
if (!reloadColorSchemes()) {
DEFAULT_COLOR = DefaultColorScheme
colors[DEFAULT_COLOR.colorName] = DEFAULT_COLOR
}
}
private lateinit var DEFAULT_COLOR: NeoColorScheme
private var colors: MutableMap<String, NeoColorScheme> = mutableMapOf()
override fun onCreateComponentObject(configVisitor: ConfigVisitor) = NeoColorScheme()
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
}
}
fun reloadColorSchemes(): Boolean {
colors.clear()
if (!reloadColorSchemes()) {
DEFAULT_COLOR = DefaultColorScheme
colors[DEFAULT_COLOR.colorName] = DEFAULT_COLOR
}
File(baseDir)
.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!")
}
override fun onCreateComponentObject(configVisitor: ConfigVisitor) = NeoColorScheme()
val component = ComponentManager.getComponent<CodeGenComponent>()
val content = component.newGenerator(colorScheme).generateCode(colorScheme)
fun reloadColorSchemes(): Boolean {
colors.clear()
File(baseDir)
.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}")
}
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
*/
object DefaultColorScheme : NeoColorScheme() {
init {
/* NOTE: Keep in sync with assets/colors/Default.nl */
colorName = "Default"
init {
/* NOTE: Keep in sync with assets/colors/Default.nl */
colorName = "Default"
foregroundColor = "#ffffff"
backgroundColor = "#14181c"
cursorColor = "#a9aaa9"
}
foregroundColor = "#ffffff"
backgroundColor = "#14181c"
cursorColor = "#a9aaa9"
}
}

View File

@ -21,176 +21,176 @@ import java.io.File
* @author kiva
*/
open class NeoColorScheme : CodeGenObject, ConfigFileBasedObject {
companion object {
const val COLOR_PREFIX = "color"
const val CONTEXT_COLOR_NAME = "colors"
const val CONTEXT_META_NAME = "color-scheme"
companion object {
const val COLOR_PREFIX = "color"
const val CONTEXT_COLOR_NAME = "colors"
const val CONTEXT_META_NAME = "color-scheme"
const val COLOR_META_NAME = "name"
const val COLOR_META_VERSION = "version"
const val COLOR_DEF_BACKGROUND = "background"
const val COLOR_DEF_FOREGROUND = "foreground"
const val COLOR_DEF_CURSOR = "cursor"
const val COLOR_META_NAME = "name"
const val COLOR_META_VERSION = "version"
const val COLOR_DEF_BACKGROUND = "background"
const val COLOR_DEF_FOREGROUND = "foreground"
const val COLOR_DEF_CURSOR = "cursor"
val COLOR_META_PATH = arrayOf(CONTEXT_META_NAME)
val COLOR_PATH = arrayOf(CONTEXT_META_NAME, CONTEXT_COLOR_NAME)
val COLOR_META_PATH = arrayOf(CONTEXT_META_NAME)
val COLOR_PATH = arrayOf(CONTEXT_META_NAME, CONTEXT_COLOR_NAME)
const val COLOR_TYPE_BEGIN = -3
const val COLOR_TYPE_END = 15
const val COLOR_TYPE_BEGIN = -3
const val COLOR_TYPE_END = 15
const val COLOR_BACKGROUND = -3
const val COLOR_FOREGROUND = -2
const val COLOR_CURSOR = -1
const val COLOR_BACKGROUND = -3
const val COLOR_FOREGROUND = -2
const val COLOR_CURSOR = -1
const val COLOR_DIM_BLACK = 0
const val COLOR_DIM_RED = 1
const val COLOR_DIM_GREEN = 2
const val COLOR_DIM_YELLOW = 3
const val COLOR_DIM_BLUE = 4
const val COLOR_DIM_MAGENTA = 5
const val COLOR_DIM_CYAN = 6
const val COLOR_DIM_WHITE = 7
const val COLOR_DIM_BLACK = 0
const val COLOR_DIM_RED = 1
const val COLOR_DIM_GREEN = 2
const val COLOR_DIM_YELLOW = 3
const val COLOR_DIM_BLUE = 4
const val COLOR_DIM_MAGENTA = 5
const val COLOR_DIM_CYAN = 6
const val COLOR_DIM_WHITE = 7
const val COLOR_BRIGHT_BLACK = 8
const val COLOR_BRIGHT_RED = 9
const val COLOR_BRIGHT_GREEN = 10
const val COLOR_BRIGHT_YELLOW = 11
const val COLOR_BRIGHT_BLUE = 12
const val COLOR_BRIGHT_MAGENTA = 13
const val COLOR_BRIGHT_CYAN = 14
const val COLOR_BRIGHT_WHITE = 15
const val COLOR_BRIGHT_BLACK = 8
const val COLOR_BRIGHT_RED = 9
const val COLOR_BRIGHT_GREEN = 10
const val COLOR_BRIGHT_YELLOW = 11
const val COLOR_BRIGHT_BLUE = 12
const val COLOR_BRIGHT_MAGENTA = 13
const val COLOR_BRIGHT_CYAN = 14
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
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
fun getColor(type: Int): String? {
validateColors()
return when (type) {
COLOR_BACKGROUND -> backgroundColor
COLOR_FOREGROUND -> foregroundColor
COLOR_CURSOR -> cursorColor
else -> {
if (type in (0 until color.size)) {
color[type]
} else {
""
}
this.color[type] = color
}
}
}
fun getColor(type: Int): String? {
validateColors()
return when (type) {
COLOR_BACKGROUND -> backgroundColor
COLOR_FOREGROUND -> foregroundColor
COLOR_CURSOR -> cursorColor
else -> {
if (type in (0 until color.size)) {
color[type]
} else {
""
}
}
}
}
fun copy(): NeoColorScheme {
val copy = NeoColorScheme()
copy.colorName = colorName
copy.backgroundColor = backgroundColor
copy.foregroundColor = foregroundColor
copy.cursorColor = cursorColor
this.color.forEach { copy.color.put(it.key, it.value) }
return copy
}
fun copy(): NeoColorScheme {
val copy = NeoColorScheme()
copy.colorName = colorName
copy.backgroundColor = backgroundColor
copy.foregroundColor = foregroundColor
copy.cursorColor = cursorColor
this.color.forEach { copy.color.put(it.key, it.value) }
return copy
}
@Throws(RuntimeException::class)
override fun onConfigLoaded(configVisitor: ConfigVisitor) {
val colorName = getMetaByVisitor(configVisitor, COLOR_META_NAME)
?: throw RuntimeException("ColorScheme must have a name")
@Throws(RuntimeException::class)
override fun onConfigLoaded(configVisitor: ConfigVisitor) {
val colorName = getMetaByVisitor(configVisitor, COLOR_META_NAME)
?: throw RuntimeException("ColorScheme must have a name")
this.colorName = colorName
this.colorVersion = getMetaByVisitor(configVisitor, COLOR_META_VERSION)
this.colorName = colorName
this.colorVersion = getMetaByVisitor(configVisitor, COLOR_META_VERSION)
backgroundColor = getColorByVisitor(configVisitor, "background")
foregroundColor = getColorByVisitor(configVisitor, "foreground")
cursorColor = getColorByVisitor(configVisitor, "cursor")
configVisitor.getContext(COLOR_PATH).getAttributes().forEach {
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.")
}
backgroundColor = getColorByVisitor(configVisitor, "background")
foregroundColor = getColorByVisitor(configVisitor, "foreground")
cursorColor = getColorByVisitor(configVisitor, "cursor")
configVisitor.getContext(COLOR_PATH).getAttributes().forEach {
if (it.key.startsWith(COLOR_PREFIX)) {
val colorIndex = try {
it.key.substringAfter(COLOR_PREFIX).toInt()
} catch (e: Exception) {
NLog.e("ExtraKey", "Failed to load extra key config: ${file.absolutePath}: ${e.localizedMessage}")
return false
-1
}
val visitor = configure.getVisitor()
onConfigLoaded(visitor)
return true
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) {
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