Compare commits
213 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8056013082 | ||
|
|
8e90545c4b | ||
|
|
426ddbacbd | ||
|
|
7e1f8a551f | ||
|
|
e169af0447 | ||
|
|
a2cb3fafee | ||
|
|
166710f14a | ||
|
|
c1a9b7726f | ||
|
|
afb339e9d8 | ||
|
|
64c23f498f | ||
|
|
1dc92b2a12 | ||
|
|
990a957383 | ||
|
|
7bb64d724c | ||
|
|
4609dd71c6 | ||
|
|
8d00f22d4c | ||
|
|
5532421ab2 | ||
|
|
d2b27978e2 | ||
|
|
30b05e9ab2 | ||
|
|
c350318c77 | ||
|
|
c9b49cef58 | ||
|
|
f9c642c672 | ||
|
|
c0a5e5f57a | ||
|
|
dfdc9b37e1 | ||
|
|
dfb22e6050 | ||
|
|
b95d84fe13 | ||
|
|
a73228b109 | ||
|
|
eaeb0930f4 | ||
|
|
95a50096cb | ||
|
|
8caeab470e | ||
|
|
6b62e65154 | ||
|
|
fb7dc21c18 | ||
|
|
d0abd17091 | ||
|
|
0550dbff9d | ||
|
|
9d7ed21f27 | ||
|
|
7e2cbd969a | ||
|
|
f9842f22fb | ||
|
|
962a43743c | ||
|
|
ef892fca0b | ||
|
|
2bf9e7b205 | ||
|
|
bc158252d6 | ||
|
|
b16f11cd87 | ||
|
|
f57232b40e | ||
|
|
f156ce259e | ||
|
|
2db6923bc4 | ||
|
|
d72fd579ee | ||
|
|
964c0b7b4f | ||
|
|
a049ea50d7 | ||
|
|
95a0878e10 | ||
|
|
5566b13073 | ||
|
|
9519727f38 | ||
|
|
33d1477d4a | ||
|
|
1cc7829847 | ||
|
|
d17bbab8ee | ||
|
|
a020d7c484 | ||
|
|
9be6470d19 | ||
|
|
491240ee3f | ||
|
|
599aaff723 | ||
|
|
20d57908a7 | ||
|
|
2104252244 | ||
|
|
f047160fd6 | ||
|
|
a2ebcdcf49 | ||
|
|
0861be363b | ||
|
|
d1c0b6abdc | ||
|
|
8714800c6b | ||
|
|
042fbfaea3 | ||
|
|
08d6d1706d | ||
|
|
cf19d43bb7 | ||
|
|
f86c7a85d3 | ||
|
|
887d7810f6 | ||
|
|
5be3099a5b | ||
|
|
bdd5c80fca | ||
|
|
cc7b6cba13 | ||
|
|
ff2f77c427 | ||
|
|
afaa91b2ca | ||
|
|
46da1fc833 | ||
|
|
746dc750df | ||
|
|
7db1f6c5a1 | ||
|
|
b7f3fdf528 | ||
|
|
6e7f777d04 | ||
|
|
fc15bd2355 | ||
|
|
a87cbdd70c | ||
|
|
fb7f7d249e | ||
|
|
026d0b495e | ||
|
|
533fa60516 | ||
|
|
dc086a1e0b | ||
|
|
2a056aeb2e | ||
|
|
9e70ebc2a6 | ||
|
|
9686127f81 | ||
|
|
395c36ee83 | ||
|
|
906ff24e76 | ||
|
|
c8af974852 | ||
|
|
481339e2f5 | ||
|
|
b2ecae63a8 | ||
|
|
a67f798f2f | ||
|
|
d69485b70b | ||
|
|
421dfcca39 | ||
|
|
3aaa0ab267 | ||
|
|
e7f9647beb | ||
|
|
5558f371b4 | ||
|
|
0882ed6470 | ||
|
|
5c02448521 | ||
|
|
17382fb190 | ||
|
|
d6eea83bfc | ||
|
|
51181c2d49 | ||
|
|
480b8a4f7e | ||
|
|
f989157f10 | ||
|
|
0e942f90a6 | ||
|
|
5b8eca46a1 | ||
|
|
493900d60b | ||
|
|
c6d6a63637 | ||
|
|
ca71265f23 | ||
|
|
46c9c4b80e | ||
|
|
6ca055bb25 | ||
|
|
ce7ad530cd | ||
|
|
d0015cbe82 | ||
|
|
9e19217f8f | ||
|
|
048af64093 | ||
|
|
a8f7bf1b6e | ||
|
|
62e229e184 | ||
|
|
36e4d94093 | ||
|
|
d2c9c5a0f0 | ||
|
|
6405180cb8 | ||
|
|
1b6919bb23 | ||
|
|
e52cd2dd41 | ||
|
|
54857d5fd4 | ||
|
|
38dd99e827 | ||
|
|
7256b04317 | ||
|
|
01a1c6de0f | ||
|
|
497fc3ecd0 | ||
|
|
b2b39abacd | ||
|
|
bee305e53f | ||
|
|
c8d2f28ed8 | ||
|
|
19eb371d23 | ||
|
|
6caaae4fd6 | ||
|
|
ed544102bc | ||
|
|
8f1ab1bc17 | ||
|
|
50337cbf9d | ||
|
|
fa9ea2db5c | ||
|
|
7a659ebd21 | ||
|
|
54bc1ed791 | ||
|
|
fe4365c94b | ||
|
|
0c13ea1bd4 | ||
|
|
862b461a07 | ||
|
|
845976be0f | ||
|
|
60bdaa3bf6 | ||
|
|
eeb873f4e4 | ||
|
|
207ddf9fdc | ||
|
|
5ca82ea095 | ||
|
|
913c474d32 | ||
|
|
657c270d97 | ||
|
|
7763931035 | ||
|
|
50005bc794 | ||
|
|
96f5ed985a | ||
|
|
c3aa9d9662 | ||
|
|
47634ca237 | ||
|
|
d9ec1bf40b | ||
|
|
bc1b742a36 | ||
|
|
86e2945069 | ||
|
|
961e06379b | ||
|
|
468185efb3 | ||
|
|
e006e36dd0 | ||
|
|
dd38965c46 | ||
|
|
79d56b778d | ||
|
|
2326c52199 | ||
|
|
f907684ef2 | ||
|
|
9d37461ac7 | ||
|
|
4de0f98fa4 | ||
|
|
f153a72592 | ||
|
|
b0f4efb0bc | ||
|
|
5c03c2d77e | ||
|
|
bce65f7db1 | ||
|
|
e18579164f | ||
|
|
16273a1981 | ||
|
|
ce82979e2b | ||
|
|
625aeab398 | ||
|
|
bad6712338 | ||
|
|
b54c7909bd | ||
|
|
4ccc703fcf | ||
|
|
7348820caf | ||
|
|
525985b1f2 | ||
|
|
ab3852d2e4 | ||
|
|
9928073e48 | ||
|
|
3091da64bc | ||
|
|
74dca95101 | ||
|
|
2a6a3b76b7 | ||
|
|
0e4ea95d74 | ||
|
|
7389dbb56f | ||
|
|
d982c71efe | ||
|
|
1b36c684d6 | ||
|
|
a6a83b1fcd | ||
|
|
d6f01bfe9a | ||
|
|
271dd7dcee | ||
|
|
95fbb810e2 | ||
|
|
1aa439311b | ||
|
|
12ddaccaf7 | ||
|
|
09fe7e5941 | ||
|
|
36cc010a87 | ||
|
|
b1aaf5abe5 | ||
|
|
9b3dc57447 | ||
|
|
f7ce206212 | ||
|
|
0deacd8fc6 | ||
|
|
65cfcffa6f | ||
|
|
463b927813 | ||
|
|
09ecd14764 | ||
|
|
89912be500 | ||
|
|
3b4e3b0e42 | ||
|
|
7a726c035c | ||
|
|
430a98e9ad | ||
|
|
01de6b4d18 | ||
|
|
1652c1dcf3 | ||
|
|
7db3200c13 | ||
|
|
9e7ca8b689 | ||
|
|
cb42c19d76 |
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Copying and distribution of this file, with or without modification,
|
||||||
|
# are permitted in any medium without royalty provided this notice is
|
||||||
|
# preserved. This file is offered as-is, without any warranty.
|
||||||
|
|
||||||
|
# EditorConfig
|
||||||
|
# http://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
# Built application files
|
# Built application files
|
||||||
build/
|
build/
|
||||||
|
*.apk
|
||||||
|
*.so
|
||||||
|
|
||||||
# Crashlytics configuations
|
# Crashlytics configuations
|
||||||
com_crashlytics_export_strings.xml
|
com_crashlytics_export_strings.xml
|
||||||
@@ -29,6 +31,7 @@ local.properties
|
|||||||
.idea/modules.xml
|
.idea/modules.xml
|
||||||
.idea/scopes/scope_settings.xml
|
.idea/scopes/scope_settings.xml
|
||||||
.idea/vcs.xml
|
.idea/vcs.xml
|
||||||
|
.idea/dictionaries/
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
# OS-specific files
|
# OS-specific files
|
||||||
|
|||||||
229
.idea/codeStyleSettings.xml
generated
Normal file
229
.idea/codeStyleSettings.xml
generated
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectCodeStyleSettingsManager">
|
||||||
|
<option name="PER_PROJECT_SETTINGS">
|
||||||
|
<value>
|
||||||
|
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||||
|
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||||
|
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||||
|
<value />
|
||||||
|
</option>
|
||||||
|
<option name="IMPORT_LAYOUT_TABLE">
|
||||||
|
<value>
|
||||||
|
<package name="android" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="com" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="junit" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="net" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="org" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="java" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="javax" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="" withSubpackages="true" static="true" />
|
||||||
|
<emptyLine />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="RIGHT_MARGIN" value="100" />
|
||||||
|
<AndroidXmlCodeStyleSettings>
|
||||||
|
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
||||||
|
</AndroidXmlCodeStyleSettings>
|
||||||
|
<Objective-C-extensions>
|
||||||
|
<option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
|
||||||
|
<option name="RELEASE_STYLE" value="IVAR" />
|
||||||
|
<option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
|
||||||
|
<file>
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||||
|
</file>
|
||||||
|
<class>
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||||
|
</class>
|
||||||
|
<extensions>
|
||||||
|
<pair source="cpp" header="h" />
|
||||||
|
<pair source="c" header="h" />
|
||||||
|
</extensions>
|
||||||
|
</Objective-C-extensions>
|
||||||
|
<XML>
|
||||||
|
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
|
||||||
|
</XML>
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>name</NAME>
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_width</NAME>
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_height</NAME>
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_.*</NAME>
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:width</NAME>
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:height</NAME>
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/gradle.xml
generated
8
.idea/gradle.xml
generated
@@ -3,15 +3,21 @@
|
|||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
<option name="disableWrapperSourceDistributionNotification" value="true" />
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="1.8" />
|
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="$PROJECT_DIR$/app" />
|
<option value="$PROJECT_DIR$/app" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="myModules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
71
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
71
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="AndroidLintLogConditional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AndroidLintNegativeMargin" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DeprecatedAPI" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="DuplicateSwitchCase" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="EmptyStatementBody" enabled="false" level="WARNING" enabled_by_default="false">
|
||||||
|
<option name="m_reportEmptyBlocks" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="EndlessLoop" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="EqualityInConditionalOperator" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="Finalize" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreTrivialFinalizers" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="FinalizeNotProtected" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="FormatSpecifiers" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="FunctionImplicitDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="HidesUpperScope" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="HidingNonVirtualFunction" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="ImplicitIntegerAndEnumConversion" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="ImplicitPointerAndIntegerConversion" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="IncompatibleEnums" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="IncompatibleInitializers" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="IncompatiblePointers" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="InstanceofChain" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreInstanceofOnLibraryClasses" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="KRUnspecifiedParameters" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="LocalValueEscapesScope" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
|
||||||
|
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
|
||||||
|
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="MissingReturn" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="MissingSwitchCase" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="NotImplementedFunctions" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="NotInitializedVariable" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="NotSuperclass" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="OCDFAInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="OCLoopDoesntUseConditionVariableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="OCSimplifyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="OCUnusedGlobalDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="OCUnusedStructInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="OCUnusedTemplateParameterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="OnDemandImport" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PrivateMemberAccessBetweenOuterAndInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ResourceNotFoundInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="SamePackageImport" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SignednessMismatch" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="UnnecessaryFullyQualifiedName" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_ignoreJavadoc" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="UnreachableCode" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="UnusedExpressionResult" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="UnusedImportStatement" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="UnusedLocalization" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="UnusedParameter" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="UnusedValue" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="ValueMayNotFitIntoReceiver" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="VariableNotUsedInsideIf" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="PROJECT_PROFILE" value="Project Default" />
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="true" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
language: android
|
|
||||||
sudo: false
|
sudo: false
|
||||||
|
language: android
|
||||||
|
jdk: oraclejdk8
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
@@ -11,9 +12,9 @@ android:
|
|||||||
components:
|
components:
|
||||||
- platform-tools
|
- platform-tools
|
||||||
- tools
|
- tools
|
||||||
- build-tools-23.0.1
|
- build-tools-24.0.1
|
||||||
- android-23
|
- android-24
|
||||||
- sys-img-x86-android-23
|
- extra-android-m2repository
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./gradlew testDebugUnitTest
|
- ./gradlew testDebugUnitTest
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -1,11 +1,13 @@
|
|||||||
Termux app
|
Termux app
|
||||||
==========
|
==========
|
||||||
[](https://travis-ci.org/termux/termux-app)
|
[](https://travis-ci.org/termux/termux-app)
|
||||||
|
[](https://gitter.im/termux/termux)
|
||||||
|
|
||||||
|
|
||||||
Termux is an Android terminal app and Linux environment.
|
Termux is an Android terminal app and Linux environment.
|
||||||
|
|
||||||
* [Termux on Google Play](http://play.google.com/store/apps/details?id=com.termux)
|
* [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux)
|
||||||
|
* [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux)
|
||||||
* [termux.com](http://termux.com)
|
* [termux.com](http://termux.com)
|
||||||
* [Termux Help](http://termux.com/help/)
|
* [Termux Help](http://termux.com/help/)
|
||||||
* [Termux app on GitHub](https://github.com/termux/termux-app)
|
* [Termux app on GitHub](https://github.com/termux/termux-app)
|
||||||
@@ -14,11 +16,11 @@ Termux is an Android terminal app and Linux environment.
|
|||||||
|
|
||||||
License
|
License
|
||||||
=======
|
=======
|
||||||
Released under the GPLv3 license. Contains code from `Terminal Emulator for Android` which is released under the Apache License.
|
Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html). Contains code from `Terminal Emulator for Android` which is released under [the Apache License 2.0](https://www.apache.org/licenses/).
|
||||||
|
|
||||||
Building JNI libraries
|
Building JNI libraries
|
||||||
======================
|
======================
|
||||||
For ease of use, the JNI libraries are checked into version control. Execute the `build-jnilibs.sh` script to rebuild them.
|
Execute the `build-jnilibs.sh` script to build the required JNI libraries.
|
||||||
|
|
||||||
Terminal resources
|
Terminal resources
|
||||||
==================
|
==================
|
||||||
@@ -29,9 +31,9 @@ Terminal resources
|
|||||||
Terminal emulators
|
Terminal emulators
|
||||||
==================
|
==================
|
||||||
* VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal. [Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+), and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED).
|
* VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal. [Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+), and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED).
|
||||||
* iTerm 2: Mac terminal application. [Source](https://github.com/gnachman/iTerm2), [Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html) (which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)).
|
* iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2), [Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html) (which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)).
|
||||||
* Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository), in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests), [Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole) and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole).
|
* Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository), in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests), [Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole) and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole).
|
||||||
* hterm: Javascript terminal implementation from chromium. [Source](https://github.com/chromium/hterm), including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js), and [google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm).
|
* hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm), including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js), and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm).
|
||||||
* xterm: The grandfather of terminal emulators. [Source](http://invisible-island.net/datafiles/release/xterm.tar.gz).
|
* xterm: The grandfather of terminal emulators. [Source](http://invisible-island.net/datafiles/release/xterm.tar.gz).
|
||||||
* Connectbot: Android SSH client. [Source](https://github.com/connectbot/connectbot)
|
* Connectbot: Android SSH client. [Source](https://github.com/connectbot/connectbot)
|
||||||
* Android Terminal Emulator: Android terminal app which Termux terminal handling is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator).
|
* Android Terminal Emulator: Android terminal app which Termux terminal handling is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator).
|
||||||
|
|||||||
@@ -1,44 +1,33 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 23
|
compileSdkVersion 24
|
||||||
buildToolsVersion "23.0.1"
|
buildToolsVersion "24.0.1"
|
||||||
|
|
||||||
sourceSets {
|
dependencies {
|
||||||
main {
|
compile 'com.android.support:support-annotations:24.2.0'
|
||||||
jni.srcDirs = []
|
compile "com.android.support:support-v4:24.2.0"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.termux"
|
applicationId "com.termux"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 22
|
targetSdkVersion 24
|
||||||
versionCode 17
|
versionCode 42
|
||||||
versionName "0.17"
|
versionName "0.42"
|
||||||
}
|
|
||||||
|
|
||||||
signingConfigs {
|
ndk {
|
||||||
release {
|
moduleName "libtermux"
|
||||||
if (System.getenv("TRAVIS")) {
|
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
||||||
storeFile rootProject.file('travis.keystore')
|
cFlags "-std=c11 -Wall -Wextra -Os -fno-stack-protector -nostdlib -Wl,--gc-sections"
|
||||||
storePassword 'abcdef'
|
|
||||||
keyAlias 'travis'
|
|
||||||
keyPassword 'abcdef'
|
|
||||||
} else {
|
|
||||||
storeFile new File(TERMUX_KEYSTORE_FILE)
|
|
||||||
storePassword TERMUX_KEYSTORE_PASSWORD
|
|
||||||
keyAlias TERMUX_KEYSTORE_ALIAS
|
|
||||||
keyPassword TERMUX_KEYSTORE_PASSWORD
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
signingConfig signingConfigs.release
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@
|
|||||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
<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.software.leanback" android:required="false" />
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="22" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
@@ -16,11 +14,13 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
android:fullBackupContent="@xml/backupscheme"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:banner="@drawable/banner"
|
android:banner="@drawable/banner"
|
||||||
android:label="@string/application_name"
|
android:label="@string/application_name"
|
||||||
android:theme="@style/Theme.Termux"
|
android:theme="@style/Theme.Termux"
|
||||||
android:supportsRtl="false" >
|
android:supportsRtl="false" >
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.termux.app.TermuxActivity"
|
android:name="com.termux.app.TermuxActivity"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
@@ -35,13 +35,59 @@
|
|||||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.termux.app.TermuxHelpActivity"
|
android:name="com.termux.app.TermuxHelpActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/application_help" />
|
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
|
||||||
|
android:parentActivityName=".app.TermuxActivity"
|
||||||
|
android:label="@string/application_name" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.termux.filepicker.TermuxFileReceiverActivity"
|
||||||
|
android:label="@string/application_name"
|
||||||
|
android:taskAffinity="com.termux.filereceiver"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:noHistory="true">
|
||||||
|
<!-- Accept multiple file types when sending. -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="application/*" />
|
||||||
|
<data android:mimeType="audio/*" />
|
||||||
|
<data android:mimeType="image/*" />
|
||||||
|
<data android:mimeType="message/*" />
|
||||||
|
<data android:mimeType="multipart/*" />
|
||||||
|
<data android:mimeType="text/*" />
|
||||||
|
<data android:mimeType="video/*" />
|
||||||
|
</intent-filter>
|
||||||
|
<!-- Be more restrictive for viewing files, restricting ourselves to text files. -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/*" />
|
||||||
|
<data android:mimeType="application/json" />
|
||||||
|
<data android:mimeType="application/*xml*" />
|
||||||
|
<data android:mimeType="application/*latex*" />
|
||||||
|
<data android:mimeType="application/javascript" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name=".filepicker.TermuxDocumentsProvider"
|
||||||
|
android:authorities="com.termux.documents"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||||
|
</intent-filter>
|
||||||
|
</provider>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="com.termux.app.TermuxService"
|
android:name="com.termux.app.TermuxService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,229 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<title>Termux Help</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
html { font-family: 'sans-serif-light', sans-serif; height: 100%; margin: auto; padding: 0; color: black; background-color: white; }
|
|
||||||
.page { max-width: 820px; margin: auto; padding: 0 1em; }
|
|
||||||
body { margin-left: auto; margin-right: auto; margin-top: 0; padding: 0; width: 100%; }
|
|
||||||
p { font-size: 16px; line-height: 1.3em; }
|
|
||||||
ul.index { padding-left: 0; }
|
|
||||||
.index li { list-style-type: none; line-height: 1.8em; }
|
|
||||||
dt { margin-left: 1em; list-style-type: bullet; }
|
|
||||||
a, a:visited { color: #0000EE }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="page help">
|
|
||||||
<h1 id="index">Termux Help</h1>
|
|
||||||
|
|
||||||
<ul class="index">
|
|
||||||
<li><a href="#introduction">Introduction</a></li>
|
|
||||||
<li><a href="#user_interface">User interface</a></li>
|
|
||||||
<li><a href="#touch_keyboard">Using a touch keyboard</a></li>
|
|
||||||
<li><a href="#hardware_keyboard">Using a hardware keyboard</a></li>
|
|
||||||
<li><a href="#package_management">Package management</a></li>
|
|
||||||
<li><a href="#text_editing">Text editing</a></li>
|
|
||||||
<li><a href="#using_ssh">Using SSH</a></li>
|
|
||||||
<li><a href="#interactive_shells">Interactive shells</a></li>
|
|
||||||
<li><a href="#termux_android">Termux and Android</a></li>
|
|
||||||
<li><a href="#add_on_api">Add-on: API</a></li>
|
|
||||||
<li><a href="#add_on_float">Add-on: Float</a></li>
|
|
||||||
<li><a href="#add_on_styling">Add-on: Styling</a></li>
|
|
||||||
<li><a href="#add_on_widget">Add-on: Widget</a></li>
|
|
||||||
<li><a href="#source_and_licenses">Source and licenses</a>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="introduction">Introduction</h2>
|
|
||||||
<p>Termux is a terminal emulator for Android combined with a collection of packages for command line software. This help
|
|
||||||
explains both the terminal interface and the packaging tool available from inside the terminal.</p>
|
|
||||||
<p>Want to ask a question, report a bug or have an idea for a new package or feature?
|
|
||||||
Visit the <a href="https://plus.google.com/communities/101692629528551299417">Google+ Termux Community</a>!</p>
|
|
||||||
|
|
||||||
<h2 id="user_interface">User interface</h2>
|
|
||||||
<p>At launch Termux shows a terminal interface, whose text size can be adjusted by pinch zooming or double tapping
|
|
||||||
and pulling the content towards or from you.</p>
|
|
||||||
<p>Besides the terminal (with keyboard shortcuts explained below) there are three additional interface elements available:
|
|
||||||
A <strong>context menu</strong>, <strong>navigation drawer</strong>
|
|
||||||
and <strong>notification</strong>.</p>
|
|
||||||
<p>The <strong>context menu</strong> can be shown by long pressing anywhere on the terminal. It provides menu entries for:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Selecting and pasting text.</li>
|
|
||||||
<li>Sharing text from the terminal to other apps (e.g. email or SMS)</li>
|
|
||||||
<li>Resetting the terminal if it gets stuck.</li>
|
|
||||||
<li>Switching the terminal to full-screen.</li>
|
|
||||||
<li>Hangup (exiting the current terminal session).</li>
|
|
||||||
<li>Styling the terminal by selecting a font and a color scheme.</li>
|
|
||||||
<li>Showing this help page.</li>
|
|
||||||
</ul>
|
|
||||||
<p>The <strong>navigation drawer</strong> is revealed by swiping from the left part of the screen. It has three
|
|
||||||
elements:</p>
|
|
||||||
<ul>
|
|
||||||
<li>A list of sessions. Clicking on a session shows it in the terminal while long pressing allows you to specify a session title.</li>
|
|
||||||
<li>A button to toggle visibility of the touch keyboard.</li>
|
|
||||||
<li>A button to create new terminal sessions (long press for creating a named session or a fail-safe one).</li>
|
|
||||||
</ul>
|
|
||||||
<p>The <strong>notification</strong>, available when a terminal session is running, is available by pulling down the notification menu.
|
|
||||||
Pressing the notification leads to the most current terminal session. The notification may also be expanded
|
|
||||||
(by pinch-zooming or performing a single-finger glide) to expose three actions:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Exiting all running terminal sessions.</li>
|
|
||||||
<li>Use a wake lock to avoid entering sleep mode.</li>
|
|
||||||
<li>Use a high performance wifi lock to maximize wifi performance.</li>
|
|
||||||
</ul>
|
|
||||||
<p>With a wake or wifi lock held the notification and Termux background processes will be available even if no terminal
|
|
||||||
session is running, which allows server and other background processes to run more reliably.</p>
|
|
||||||
|
|
||||||
<h2 id="touch_keyboard">Using a touch keyboard</h2>
|
|
||||||
<p>Using the Ctrl key is necessary for working with a terminal - but most touch keyboards
|
|
||||||
does not include one. For that purpose Termux uses the <em>Volume down</em> button to emulate
|
|
||||||
the Ctrl key. For example, pressing <em>Volume down+L</em> on a touch keyboard sends the same input as
|
|
||||||
pressing <em>Ctrl+L</em> on a hardware keyboard. The result of using Ctrl in combination
|
|
||||||
with a key depends on which program is used, but for many command line tools the following
|
|
||||||
shortcuts works:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Ctrl+A → Move cursor to the beginning of line.</li>
|
|
||||||
<li>Ctrl+C → Abort (send SIGINT to) current process.</li>
|
|
||||||
<li>Ctrl+D → Logout of a terminal session.</li>
|
|
||||||
<li>Ctrl+E → Move cursor to the end of line.</li>
|
|
||||||
<li>Ctrl+K → Delete from cursor to the end of line.</li>
|
|
||||||
<li>Ctrl+L → Clear the terminal.</li>
|
|
||||||
<li>Ctrl+Z → Suspend (send SIGTSTP to) current process.</li>
|
|
||||||
</ul>
|
|
||||||
<p>The <em>Volume up</em> key also serves as a special key to produce certain input:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Volume Up+L → | (the pipe character).</li>
|
|
||||||
<li>Volume Up+E → Escape key.</li>
|
|
||||||
<li>Volume Up+T → Tab key.</li>
|
|
||||||
<li>Volume Up+1 → F1 (and Volume Up+2 → F2, etc).</li>
|
|
||||||
<li>Volume Up+B → Alt+B, back a word when using readline.</li>
|
|
||||||
<li>Volume Up+F → Alt+F, forward a word when using readline.</li>
|
|
||||||
<li>Volume Up+W → Up arrow key.</li>
|
|
||||||
<li>Volume Up+A → Left arrow key.</li>
|
|
||||||
<li>Volume Up+S → Down arrow key.</li>
|
|
||||||
<li>Volume Up+D → Right arrow key.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="hardware_keyboard">Using a hardware keyboard</h2>
|
|
||||||
<p>The following shortcuts are available when using Termux with a hardware (e.g. bluetooth) keyboard by combining them with <em>Ctrl+Shift</em>:</p>
|
|
||||||
<ul>
|
|
||||||
<li>'C' → Create new session</li>
|
|
||||||
<li>'R' → Rename current session</li>
|
|
||||||
<li>Down arrow (or 'N') → Next session</li>
|
|
||||||
<li>Up arrow (or 'P') → Previous session</li>
|
|
||||||
<li>Right arrow → Open drawer</li>
|
|
||||||
<li>Left arrow → Close drawer</li>
|
|
||||||
<li>'F' → Toggle full screen</li>
|
|
||||||
<li>'M' → Show menu</li>
|
|
||||||
<li>'V' → Paste</li>
|
|
||||||
<li>+/- → Adjust text size</li>
|
|
||||||
<li>1-9 → Go to numbered session</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="package_management">Package management</h2>
|
|
||||||
<p>A minimal base system consisting of the Apt package manager and the busybox collection of system utilities
|
|
||||||
is installed when first starting Termux. Additional packages are available using the apt command:</p>
|
|
||||||
<dl>
|
|
||||||
<dt>apt update</dt><dd>Updates the list of available packages. This commands needs to be run initially directly after installation
|
|
||||||
and regularly afterwards to receive updates.</dd>
|
|
||||||
<dt>apt search <query></dt><dd>Search among available packages.</dd>
|
|
||||||
<dt>apt install <package></dt><dd>Install a new package.</dd>
|
|
||||||
<dt>apt upgrade</dt><dd>Upgrade outdated packages. For Apt to know about newer packages you will need to update the package index, so you will normally want to run <em>apt update</em> before upgrading.</dd>
|
|
||||||
<dt>apt show <package></dt><dd>Show information about a package.</dd>
|
|
||||||
<dt>apt list</dt><dd>List all available packages.</dd>
|
|
||||||
<dt>apt list --installed</dt><dd>List all installed packages.</dd>
|
|
||||||
<dt>apt remove <package></dt><dd>Remove an installed package.</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<p>Apt as a package manager uses a package format named <em>dpkg</em>. Normally direct use of dpkg is not necessary, but the
|
|
||||||
following two commands may be of use:</p>
|
|
||||||
<dl>
|
|
||||||
<dt>dpkg -L <package></dt>
|
|
||||||
<dd>List installed files of a package.</dd>
|
|
||||||
<dt>dpkg --verify</dt>
|
|
||||||
<dd>Verify the integrity of installed packages.</dd>
|
|
||||||
</dl>
|
|
||||||
<p>View the apt manual page (execute <em>apt install man</em> to install a man page viewer first) for more information.</p>
|
|
||||||
|
|
||||||
<h2 id="text_editing">Text editing</h2>
|
|
||||||
<p>By default the busybox version of <em>vi</em> is available. This is a barebone and somewhat unfriendly editor -
|
|
||||||
install <a href="http://www.nano-editor.org/dist/v2.2/nano.html">nano</a> for a more straight-forward editor and
|
|
||||||
<a href="http://vimdoc.sourceforge.net/htmldoc/usr_toc.html">vim</a> for a more powerful one.</p>
|
|
||||||
|
|
||||||
<h2 id="using_ssh">Using SSH</h2>
|
|
||||||
<p>By installing the <strong>openssh</strong> package (by executing <em>apt install openssh</em>) you may SSH into remote systems,
|
|
||||||
optionally putting private keys or configuration under $HOME/.ssh/.</p>
|
|
||||||
<p>If you wish to use an SSH agent to avoid entering passwords, the Termux openssh package provides
|
|
||||||
a wrapper script named <strong>ssha</strong> (note the 'a' at the end) for ssh which:</p>
|
|
||||||
<ol>
|
|
||||||
<li>Starts the ssh agent if necessary (or connect to it if already running).</li>
|
|
||||||
<li>Runs <strong>ssh-add</strong> if necessary.</li>
|
|
||||||
<li>Runs <strong>ssh</strong> with the provided arguments.</li>
|
|
||||||
</ol>
|
|
||||||
<p>This means that the agent will prompt for a key password at first run, but remember the authorization for subsequent ones.</p>
|
|
||||||
|
|
||||||
<h2 id="interactive_shells">Interactive shells</h2>
|
|
||||||
<p>The base system that is installed when first starting Termux uses the <em>bash</em> shell while zsh is available as
|
|
||||||
an installable alternative:</p>
|
|
||||||
<ul>
|
|
||||||
<li>bash - the default shell on most Linux distributions, with resources such as
|
|
||||||
<a href="http://www.tldp.org/LDP/Bash-Beginners-Guide/html/">Bash Guide for Beginners</a>,
|
|
||||||
the <a href="https://www.gnu.org/software/bash/manual/bash.html">Bash Reference Manual</a>
|
|
||||||
or the <a href="http://www.tldp.org/LDP/abs/html/">Advanced Bash-Scripting Guide</a> available.</li>
|
|
||||||
<li>zsh - a powerful shell with information available at
|
|
||||||
<a href="http://zsh.sourceforge.net/Guide/zshguide.html">A User's Guide to the Z-Shell</a>, the
|
|
||||||
<a href="http://zsh.sourceforge.net/Doc/Release/zsh_toc.html">Z Shell Manual</a> or
|
|
||||||
<a href="http://www.rayninfo.co.uk/tips/zshtips.html">ZSH Tips by ZZapper</a>.
|
|
||||||
After installing zsh through <em>apt install zsh</em>, execute <em>chsh -s zsh</em> to set it as the default login shell when starting Termux
|
|
||||||
(and change back with <em>chsh -s bash</em> if necessary).</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2 id="termux_android">Termux and Android</h2>
|
|
||||||
<p>Termux is designed to cope with the restrictions of running as an ordinary Android app without requiring root, which
|
|
||||||
leads to several differences between Termux and a traditional desktop system. The file system layout is drastically different:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Common folders such as /bin, /usr/, /var and /etc does not exist.</li>
|
|
||||||
<li>The Android system provides a basic non-standard file system hierarchy, where e.g. /system/bin contains some system binaries.</li>
|
|
||||||
<li>The user folder $HOME is inside the private file area exposed to Termux as an ordinary Android app.
|
|
||||||
Uninstalling Termux will cause this file area to be wiped - so save important files outside this area such as in /sdcard
|
|
||||||
or use a version control system such as <em>git</em>.</li>
|
|
||||||
<li>Termux installs its packages in a folder exposed through the $PREFIX environment variable (with e.g. binaries in $PREFIX/bin,
|
|
||||||
and configuration in $PREFIX/etc).</li>
|
|
||||||
<li>Shared libraries are installed in $PREFIX/lib, which are available from binaries due to Termux setting the $LD_LIBRARY_PATH
|
|
||||||
environment variable. These may clash with Android system binaries in /system/bin, which may force LD_LIBRARY_PATH to be
|
|
||||||
cleared before running system binaries.</li>
|
|
||||||
</ul>
|
|
||||||
<p>Besides the file system being different, Termux is running as a single-user system without root - each Android app is running as
|
|
||||||
its own Linux user, so running commands inside Termux may not interfere with other installed applications.</p>
|
|
||||||
<p>Running as non-root implies that ports below 1024 cannot be bound to. Many packages have been configured to have compatible
|
|
||||||
default values - the ftpd, httpd, and sshd servers default to 8021, 8080 and 8022, respectively.</p>
|
|
||||||
|
|
||||||
<h2 id="add_on_api">Add-on: API</h2>
|
|
||||||
<p>The API add-on exposes Android system functionality such as SMS messages, GPS location or the Text-to-speech functionality through command line tools.</p>
|
|
||||||
<ul><li><a href="http://play.google.com/store/apps/details?id=com.termux.api">See more and install from Google Play</a></li></ul>
|
|
||||||
|
|
||||||
<h2 id="add_on_float">Add-on: Float</h2>
|
|
||||||
<p>The Float add-on consists of a floating terminal window visible while running other apps.</p>
|
|
||||||
<ul><li><a href="http://play.google.com/store/apps/details?id=com.termux.window">See more and install from Google Play</a></li></ul>
|
|
||||||
|
|
||||||
<h2 id="add_on_styling">Add-on: Styling</h2>
|
|
||||||
<p>The Styling add-on provides color schemes and fonts to beabeautify and customize the appearance of the Termux terminal.</p>
|
|
||||||
<ul><li><a href="http://play.google.com/store/apps/details?id=com.termux.styling">See more and install from Google Play</a></li></ul>
|
|
||||||
|
|
||||||
<h2 id="add_on_widget">Add-on: Widget</h2>
|
|
||||||
<p>The Widget add-on brings a widget to your homescreen, providing links to run scripts in your $HOME/.shortcuts/ folder.</p>
|
|
||||||
<ul><li><a href="http://play.google.com/store/apps/details?id=com.termux.widget">See more and install from Google Play</a></li></ul>
|
|
||||||
|
|
||||||
<h2 id="source_and_licenses">Source and licenses</h2>
|
|
||||||
<p>Termux uses terminal emulation code from <a href="https://github.com/jackpal/Android-Terminal-Emulator">Terminal Emulator for Android</a>
|
|
||||||
which is under the <a href="https://raw.githubusercontent.com/jackpal/Android-Terminal-Emulator/master/NOTICE">Apache License, Version 2.0</a>.
|
|
||||||
Packages available through Termux are distributed under their respective licenses with scripts and patches used to build them
|
|
||||||
<a href="https://github.com/termux/termux-packages">available on github</a>.</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
112
app/src/main/java/com/termux/app/BackgroundJob.java
Normal file
112
app/src/main/java/com/termux/app/BackgroundJob.java
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package com.termux.app;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A background job launched by Termux.
|
||||||
|
*/
|
||||||
|
public final class BackgroundJob {
|
||||||
|
|
||||||
|
private static final String LOG_TAG = "termux-background";
|
||||||
|
|
||||||
|
final Process mProcess;
|
||||||
|
|
||||||
|
public BackgroundJob(File cwd, File fileToExecute, String[] args) throws IOException {
|
||||||
|
String[] env = buildEnvironment(false, cwd.getAbsolutePath());
|
||||||
|
|
||||||
|
String[] progArray = new String[args.length + 1];
|
||||||
|
|
||||||
|
mProcess = Runtime.getRuntime().exec(progArray, env, cwd);
|
||||||
|
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
int exitCode = mProcess.waitFor();
|
||||||
|
if (exitCode == 0) {
|
||||||
|
Log.i(LOG_TAG, "exited normally");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
Log.i(LOG_TAG, "exited with exit code: " + exitCode);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
InputStream stdout = mProcess.getInputStream();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));
|
||||||
|
String line;
|
||||||
|
try {
|
||||||
|
// FIXME: Long lines.
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
Log.i(LOG_TAG, line);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
|
||||||
|
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
InputStream stderr = mProcess.getErrorStream();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(stderr, StandardCharsets.UTF_8));
|
||||||
|
String line;
|
||||||
|
try {
|
||||||
|
// FIXME: Long lines.
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
Log.e(LOG_TAG, line);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] buildEnvironment(boolean failSafe, String cwd) {
|
||||||
|
new File(TermuxService.HOME_PATH).mkdirs();
|
||||||
|
|
||||||
|
if (cwd == null) cwd = TermuxService.HOME_PATH;
|
||||||
|
|
||||||
|
final String termEnv = "TERM=xterm-256color";
|
||||||
|
final String homeEnv = "HOME=" + TermuxService.HOME_PATH;
|
||||||
|
final String prefixEnv = "PREFIX=" + TermuxService.PREFIX_PATH;
|
||||||
|
final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT");
|
||||||
|
final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA");
|
||||||
|
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
||||||
|
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
||||||
|
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE");
|
||||||
|
String[] env;
|
||||||
|
if (failSafe) {
|
||||||
|
// Keep the default path so that system binaries can be used in the failsafe session.
|
||||||
|
final String pathEnv = "PATH=" + System.getenv("PATH");
|
||||||
|
return new String[]{termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv};
|
||||||
|
} else {
|
||||||
|
final String ps1Env = "PS1=$ ";
|
||||||
|
final String ldEnv = "LD_LIBRARY_PATH=" + TermuxService.PREFIX_PATH + "/lib";
|
||||||
|
final String langEnv = "LANG=en_US.UTF-8";
|
||||||
|
final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets";
|
||||||
|
final String pwdEnv = "PWD=" + cwd;
|
||||||
|
|
||||||
|
return new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,21 +3,42 @@ package com.termux.app;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
|
import android.text.Selection;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
|
import android.view.KeyEvent;
|
||||||
import android.view.ViewGroup.LayoutParams;
|
import android.view.ViewGroup.LayoutParams;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
final class DialogUtils {
|
public final class DialogUtils {
|
||||||
|
|
||||||
public interface TextSetListener {
|
public interface TextSetListener {
|
||||||
void onTextSet(String text);
|
void onTextSet(String text);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void textInput(Activity activity, int titleText, int positiveButtonText, String initialText, final TextSetListener onPositive) {
|
public static void textInput(Activity activity, int titleText, String initialText,
|
||||||
|
int positiveButtonText, final TextSetListener onPositive,
|
||||||
|
int neutralButtonText, final TextSetListener onNeutral,
|
||||||
|
int negativeButtonText, final TextSetListener onNegative,
|
||||||
|
final DialogInterface.OnDismissListener onDismiss) {
|
||||||
final EditText input = new EditText(activity);
|
final EditText input = new EditText(activity);
|
||||||
input.setSingleLine();
|
input.setSingleLine();
|
||||||
if (initialText != null) input.setText(initialText);
|
if (initialText != null) {
|
||||||
|
input.setText(initialText);
|
||||||
|
Selection.setSelection(input.getText(), initialText.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
final AlertDialog[] dialogHolder = new AlertDialog[1];
|
||||||
|
input.setImeActionLabel(activity.getResources().getString(positiveButtonText), KeyEvent.KEYCODE_ENTER);
|
||||||
|
input.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
|
onPositive.onTextSet(input.getText().toString());
|
||||||
|
dialogHolder[0].dismiss();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, activity.getResources().getDisplayMetrics());
|
float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, activity.getResources().getDisplayMetrics());
|
||||||
// https://www.google.com/design/spec/components/dialogs.html#dialogs-specs
|
// https://www.google.com/design/spec/components/dialogs.html#dialogs-specs
|
||||||
@@ -27,17 +48,43 @@ final class DialogUtils {
|
|||||||
LinearLayout layout = new LinearLayout(activity);
|
LinearLayout layout = new LinearLayout(activity);
|
||||||
layout.setOrientation(LinearLayout.VERTICAL);
|
layout.setOrientation(LinearLayout.VERTICAL);
|
||||||
layout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
|
layout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
|
||||||
// layout.setGravity(Gravity.CLIP_VERTICAL);
|
|
||||||
layout.setPadding(paddingTopAndSides, paddingTopAndSides, paddingTopAndSides, paddingBottom);
|
layout.setPadding(paddingTopAndSides, paddingTopAndSides, paddingTopAndSides, paddingBottom);
|
||||||
layout.addView(input);
|
layout.addView(input);
|
||||||
|
|
||||||
new AlertDialog.Builder(activity).setTitle(titleText).setView(layout).setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() {
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity)
|
||||||
|
.setTitle(titleText).setView(layout)
|
||||||
|
.setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface d, int whichButton) {
|
public void onClick(DialogInterface d, int whichButton) {
|
||||||
onPositive.onTextSet(input.getText().toString());
|
onPositive.onTextSet(input.getText().toString());
|
||||||
}
|
}
|
||||||
}).setNegativeButton(android.R.string.cancel, null).show();
|
});
|
||||||
input.requestFocus();
|
|
||||||
|
if (onNeutral != null) {
|
||||||
|
builder.setNeutralButton(neutralButtonText, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
onNeutral.onTextSet(input.getText().toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onNegative == null) {
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
} else {
|
||||||
|
builder.setNegativeButton(negativeButtonText, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
onNegative.onTextSet(input.getText().toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onDismiss != null) builder.setOnDismissListener(onDismiss);
|
||||||
|
|
||||||
|
dialogHolder[0] = builder.create();
|
||||||
|
dialogHolder[0].setCanceledOnTouchOutside(false);
|
||||||
|
dialogHolder[0].show();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
177
app/src/main/java/com/termux/app/ExtraKeysView.java
Normal file
177
app/src/main/java/com/termux/app/ExtraKeysView.java
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
package com.termux.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.GridLayout;
|
||||||
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
|
import com.termux.R;
|
||||||
|
import com.termux.terminal.TerminalSession;
|
||||||
|
import com.termux.view.TerminalView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A view showing extra keys (such as Escape, Ctrl, Alt) not normally available on an Android soft
|
||||||
|
* keyboard.
|
||||||
|
*/
|
||||||
|
public final class ExtraKeysView extends GridLayout {
|
||||||
|
|
||||||
|
private static final int TEXT_COLOR = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
public ExtraKeysView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sendKey(View view, String keyName) {
|
||||||
|
int keyCode = 0;
|
||||||
|
String chars = null;
|
||||||
|
switch (keyName) {
|
||||||
|
case "ESC":
|
||||||
|
keyCode = KeyEvent.KEYCODE_ESCAPE;
|
||||||
|
break;
|
||||||
|
case "TAB":
|
||||||
|
keyCode = KeyEvent.KEYCODE_TAB;
|
||||||
|
break;
|
||||||
|
case "▲":
|
||||||
|
keyCode = KeyEvent.KEYCODE_DPAD_UP;
|
||||||
|
break;
|
||||||
|
case "◀":
|
||||||
|
keyCode = KeyEvent.KEYCODE_DPAD_LEFT;
|
||||||
|
break;
|
||||||
|
case "▶":
|
||||||
|
keyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
|
||||||
|
break;
|
||||||
|
case "▼":
|
||||||
|
keyCode = KeyEvent.KEYCODE_DPAD_DOWN;
|
||||||
|
break;
|
||||||
|
case "―":
|
||||||
|
chars = "-";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
chars = keyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyCode > 0) {
|
||||||
|
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
||||||
|
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
||||||
|
} else {
|
||||||
|
TerminalView terminalView = (TerminalView) view.findViewById(R.id.terminal_view);
|
||||||
|
TerminalSession session = terminalView.getCurrentSession();
|
||||||
|
if (session != null) session.write(chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ToggleButton controlButton;
|
||||||
|
private ToggleButton altButton;
|
||||||
|
private ToggleButton fnButton;
|
||||||
|
|
||||||
|
public boolean readControlButton() {
|
||||||
|
if (controlButton.isPressed()) return true;
|
||||||
|
boolean result = controlButton.isChecked();
|
||||||
|
if (result) {
|
||||||
|
controlButton.setChecked(false);
|
||||||
|
controlButton.setTextColor(TEXT_COLOR);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean readAltButton() {
|
||||||
|
if (altButton.isPressed()) return true;
|
||||||
|
boolean result = altButton.isChecked();
|
||||||
|
if (result) {
|
||||||
|
altButton.setChecked(false);
|
||||||
|
altButton.setTextColor(TEXT_COLOR);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean readFnButton() {
|
||||||
|
if (fnButton.isPressed()) return true;
|
||||||
|
boolean result = fnButton.isChecked();
|
||||||
|
if (result) {
|
||||||
|
fnButton.setChecked(false);
|
||||||
|
fnButton.setTextColor(TEXT_COLOR);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reload() {
|
||||||
|
altButton = controlButton = null;
|
||||||
|
removeAllViews();
|
||||||
|
|
||||||
|
String[][] buttons = {
|
||||||
|
{"ESC", "CTRL", "ALT", "TAB", "―", "/", "|"}
|
||||||
|
};
|
||||||
|
|
||||||
|
final int rows = buttons.length;
|
||||||
|
final int cols = buttons[0].length;
|
||||||
|
|
||||||
|
setRowCount(rows);
|
||||||
|
setColumnCount(cols);
|
||||||
|
|
||||||
|
for (int row = 0; row < rows; row++) {
|
||||||
|
for (int col = 0; col < cols; col++) {
|
||||||
|
final String buttonText = buttons[row][col];
|
||||||
|
|
||||||
|
Button button;
|
||||||
|
switch (buttonText) {
|
||||||
|
case "CTRL":
|
||||||
|
button = controlButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
||||||
|
button.setClickable(true);
|
||||||
|
break;
|
||||||
|
case "ALT":
|
||||||
|
button = altButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
||||||
|
button.setClickable(true);
|
||||||
|
break;
|
||||||
|
case "FN":
|
||||||
|
button = fnButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
||||||
|
button.setClickable(true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.setText(buttonText);
|
||||||
|
button.setTextColor(TEXT_COLOR);
|
||||||
|
|
||||||
|
final Button finalButton = button;
|
||||||
|
button.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
View root = getRootView();
|
||||||
|
switch (buttonText) {
|
||||||
|
case "CTRL":
|
||||||
|
case "ALT":
|
||||||
|
case "FN":
|
||||||
|
ToggleButton self = (ToggleButton) finalButton;
|
||||||
|
self.setChecked(self.isChecked());
|
||||||
|
self.setTextColor(self.isChecked() ? 0xFF80DEEA : TEXT_COLOR);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sendKey(root, buttonText);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
GridLayout.LayoutParams param = new GridLayout.LayoutParams();
|
||||||
|
param.height = param.width = 0;
|
||||||
|
param.rightMargin = param.topMargin = 0;
|
||||||
|
param.setGravity(Gravity.LEFT);
|
||||||
|
float weight = "▲▼◀▶".contains(buttonText) ? 0.7f : 1.f;
|
||||||
|
param.columnSpec = GridLayout.spec(col, GridLayout.FILL, weight);
|
||||||
|
param.rowSpec = GridLayout.spec(row, GridLayout.FILL, 1.f);
|
||||||
|
button.setLayoutParams(param);
|
||||||
|
|
||||||
|
addView(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,68 +1,67 @@
|
|||||||
package com.termux.app;
|
package com.termux.app;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.graphics.Color;
|
||||||
import android.graphics.Rect;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.os.Build;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.view.Window;
|
import com.termux.R;
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.FrameLayout.LayoutParams;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to make the touch keyboard and immersive mode work with full screen activities.
|
* Utility to manage full screen immersive mode.
|
||||||
*
|
* <p/>
|
||||||
* See https://code.google.com/p/android/issues/detail?id=5497
|
* See https://code.google.com/p/android/issues/detail?id=5497
|
||||||
*/
|
*/
|
||||||
final class FullScreenHelper implements ViewTreeObserver.OnGlobalLayoutListener {
|
final class FullScreenHelper {
|
||||||
|
|
||||||
private boolean mEnabled = false;
|
private boolean mEnabled = false;
|
||||||
private final Activity mActivity;
|
final TermuxActivity mActivity;
|
||||||
private final Rect mWindowRect = new Rect();
|
|
||||||
|
|
||||||
public FullScreenHelper(Activity activity) {
|
public FullScreenHelper(TermuxActivity activity) {
|
||||||
this.mActivity = activity;
|
this.mActivity = activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setImmersive(boolean enabled) {
|
public void setImmersive(boolean enabled) {
|
||||||
Window win = mActivity.getWindow();
|
if (enabled == mEnabled) return;
|
||||||
|
|
||||||
if (enabled == mEnabled) {
|
|
||||||
if (!enabled) win.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mEnabled = enabled;
|
mEnabled = enabled;
|
||||||
|
|
||||||
final View childViewOfContent = ((FrameLayout) mActivity.findViewById(android.R.id.content)).getChildAt(0);
|
View decorView = mActivity.getWindow().getDecorView();
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
win.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
decorView.setOnSystemUiVisibilityChangeListener
|
||||||
setImmersiveMode();
|
(new View.OnSystemUiVisibilityChangeListener() {
|
||||||
childViewOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this);
|
|
||||||
} else {
|
|
||||||
win.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
||||||
win.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
|
||||||
childViewOfContent.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
|
||||||
((LayoutParams) childViewOfContent.getLayoutParams()).height = android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setImmersiveMode() {
|
|
||||||
mActivity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onGlobalLayout() {
|
public void onSystemUiVisibilityChange(int visibility) {
|
||||||
final View childViewOfContent = ((FrameLayout) mActivity.findViewById(android.R.id.content)).getChildAt(0);
|
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
||||||
|
if (mActivity.mSettings.isShowExtraKeys()) {
|
||||||
if (mEnabled) setImmersiveMode();
|
mActivity.findViewById(R.id.viewpager).setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
childViewOfContent.getWindowVisibleDisplayFrame(mWindowRect);
|
|
||||||
int usableHeightNow = Math.min(mWindowRect.height(), childViewOfContent.getRootView().getHeight());
|
|
||||||
FrameLayout.LayoutParams layout = (LayoutParams) childViewOfContent.getLayoutParams();
|
|
||||||
if (layout.height != usableHeightNow) {
|
|
||||||
layout.height = usableHeightNow;
|
|
||||||
childViewOfContent.requestLayout();
|
|
||||||
}
|
}
|
||||||
|
setImmersiveMode();
|
||||||
|
} else {
|
||||||
|
mActivity.findViewById(R.id.viewpager).setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setImmersiveMode();
|
||||||
|
} else {
|
||||||
|
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
||||||
|
decorView.setOnSystemUiVisibilityChangeListener(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isColorLight(int color) {
|
||||||
|
double darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
|
||||||
|
return darkness < 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setImmersiveMode() {
|
||||||
|
int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
|
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||||
|
| View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||||
|
int color = ((ColorDrawable) mActivity.getWindow().getDecorView().getBackground()).getColor();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isColorLight(color))
|
||||||
|
flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||||
|
mActivity.getWindow().getDecorView().setSystemUiVisibility(flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,8 @@
|
|||||||
package com.termux.app;
|
package com.termux.app;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import android.Manifest;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import com.termux.R;
|
|
||||||
import com.termux.drawer.DrawerLayout;
|
|
||||||
import com.termux.terminal.TerminalSession;
|
|
||||||
import com.termux.terminal.TerminalSession.SessionChangedCallback;
|
|
||||||
import com.termux.view.TerminalKeyListener;
|
|
||||||
import com.termux.view.TerminalView;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
@@ -27,18 +16,27 @@ import android.content.DialogInterface.OnShowListener;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.content.res.Resources;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.media.AudioAttributes;
|
||||||
|
import android.media.SoundPool;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.view.PagerAdapter;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.support.v4.widget.DrawerLayout;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
@@ -46,24 +44,42 @@ import android.view.KeyEvent;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.View.OnKeyListener;
|
|
||||||
import android.view.View.OnLongClickListener;
|
import android.view.View.OnLongClickListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowManager;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
import android.widget.AdapterView.OnItemLongClickListener;
|
import android.widget.AdapterView.OnItemLongClickListener;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.EditText;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.termux.R;
|
||||||
|
import com.termux.terminal.EmulatorDebug;
|
||||||
|
import com.termux.terminal.TerminalColors;
|
||||||
|
import com.termux.terminal.TerminalSession;
|
||||||
|
import com.termux.terminal.TerminalSession.SessionChangedCallback;
|
||||||
|
import com.termux.terminal.TextStyle;
|
||||||
|
import com.termux.view.TerminalView;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A terminal emulator activity.
|
* A terminal emulator activity.
|
||||||
*
|
* <p/>
|
||||||
* See
|
* See
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>http://www.mongrel-phones.com.au/default/how_to_make_a_local_service_and_bind_to_it_in_android</li>
|
* <li>http://www.mongrel-phones.com.au/default/how_to_make_a_local_service_and_bind_to_it_in_android</li>
|
||||||
@@ -73,7 +89,8 @@ import android.widget.Toast;
|
|||||||
*/
|
*/
|
||||||
public final class TermuxActivity extends Activity implements ServiceConnection {
|
public final class TermuxActivity extends Activity implements ServiceConnection {
|
||||||
|
|
||||||
private static final int CONTEXTMENU_SELECT_ID = 0;
|
private static final int CONTEXTMENU_SELECT_URL_ID = 0;
|
||||||
|
private static final int CONTEXTMENU_SHARE_TRANSCRIPT_ID = 1;
|
||||||
private static final int CONTEXTMENU_PASTE_ID = 3;
|
private static final int CONTEXTMENU_PASTE_ID = 3;
|
||||||
private static final int CONTEXTMENU_KILL_PROCESS_ID = 4;
|
private static final int CONTEXTMENU_KILL_PROCESS_ID = 4;
|
||||||
private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5;
|
private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5;
|
||||||
@@ -83,11 +100,17 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
private static final int MAX_SESSIONS = 8;
|
private static final int MAX_SESSIONS = 8;
|
||||||
|
|
||||||
|
private static final int REQUESTCODE_PERMISSION_STORAGE = 1234;
|
||||||
|
|
||||||
private static final String RELOAD_STYLE_ACTION = "com.termux.app.reload_style";
|
private static final String RELOAD_STYLE_ACTION = "com.termux.app.reload_style";
|
||||||
|
|
||||||
/** The main view of the activity showing the terminal. */
|
/** The main view of the activity showing the terminal. Initialized in onCreate(). */
|
||||||
|
@SuppressWarnings("NullableProblems")
|
||||||
|
@NonNull
|
||||||
TerminalView mTerminalView;
|
TerminalView mTerminalView;
|
||||||
|
|
||||||
|
ExtraKeysView mExtraKeysView;
|
||||||
|
|
||||||
final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this);
|
final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this);
|
||||||
|
|
||||||
TermuxPreferences mSettings;
|
TermuxPreferences mSettings;
|
||||||
@@ -111,162 +134,175 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
*/
|
*/
|
||||||
boolean mIsVisible;
|
boolean mIsVisible;
|
||||||
|
|
||||||
|
final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
|
||||||
|
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||||
|
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
|
||||||
|
int mBellSoundId;
|
||||||
|
|
||||||
private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() {
|
private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
if (mIsVisible) {
|
if (mIsVisible) {
|
||||||
String whatToReload = intent.getStringExtra(RELOAD_STYLE_ACTION);
|
String whatToReload = intent.getStringExtra(RELOAD_STYLE_ACTION);
|
||||||
if (whatToReload == null || "colors".equals(whatToReload)) mTerminalView.checkForColors();
|
if ("storage".equals(whatToReload)) {
|
||||||
if (whatToReload == null || "font".equals(whatToReload)) mTerminalView.checkForTypeface();
|
if (ensureStoragePermissionGranted())
|
||||||
|
TermuxInstaller.setupStorageSymlinks(TermuxActivity.this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checkForFontAndColors();
|
||||||
|
mSettings.reloadFromProperties(TermuxActivity.this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void checkForFontAndColors() {
|
||||||
|
try {
|
||||||
|
// Hard-coded paths since this file is used also in Termux:Float.
|
||||||
|
@SuppressLint("SdCardPath") File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
|
||||||
|
@SuppressLint("SdCardPath") File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
|
||||||
|
|
||||||
|
final Properties props = new Properties();
|
||||||
|
if (colorsFile.isFile()) {
|
||||||
|
try (InputStream in = new FileInputStream(colorsFile)) {
|
||||||
|
props.load(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TerminalColors.COLOR_SCHEME.updateWith(props);
|
||||||
|
TerminalSession session = getCurrentTermSession();
|
||||||
|
if (session != null && session.getEmulator() != null) {
|
||||||
|
session.getEmulator().mColors.reset();
|
||||||
|
}
|
||||||
|
updateBackgroundColor();
|
||||||
|
|
||||||
|
final Typeface newTypeface = (fontFile.exists() && fontFile.length() > 0) ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
|
||||||
|
mTerminalView.setTypeface(newTypeface);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(EmulatorDebug.LOG_TAG, "Error in checkForFontAndColors()", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateBackgroundColor() {
|
||||||
|
TerminalSession session = getCurrentTermSession();
|
||||||
|
if (session != null && session.getEmulator() != null) {
|
||||||
|
getWindow().getDecorView().setBackgroundColor(session.getEmulator().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** For processes to access shared internal storage (/sdcard) we need this permission. */
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
public boolean ensureStoragePermissionGranted() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE_PERMISSION_STORAGE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Always granted before Android 6.0.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
// Prevent overdraw:
|
mSettings = new TermuxPreferences(this);
|
||||||
getWindow().getDecorView().setBackground(null);
|
|
||||||
|
|
||||||
setContentView(R.layout.drawer_layout);
|
setContentView(R.layout.drawer_layout);
|
||||||
mTerminalView = (TerminalView) findViewById(R.id.terminal_view);
|
mTerminalView = (TerminalView) findViewById(R.id.terminal_view);
|
||||||
mSettings = new TermuxPreferences(this);
|
mTerminalView.setOnKeyListener(new TermuxKeyListener(this));
|
||||||
|
|
||||||
mTerminalView.setTextSize(mSettings.getFontSize());
|
mTerminalView.setTextSize(mSettings.getFontSize());
|
||||||
mFullScreenHelper.setImmersive(mSettings.isFullScreen());
|
mFullScreenHelper.setImmersive(mSettings.isFullScreen());
|
||||||
mTerminalView.requestFocus();
|
mTerminalView.requestFocus();
|
||||||
|
|
||||||
OnKeyListener keyListener = new OnKeyListener() {
|
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
|
||||||
|
if (mSettings.isShowExtraKeys()) viewPager.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
viewPager.setAdapter(new PagerAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
public int getCount() {
|
||||||
if (event.getAction() != KeyEvent.ACTION_DOWN) return false;
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
final TerminalSession currentSession = getCurrentTermSession();
|
@Override
|
||||||
|
public boolean isViewFromObject(View view, Object object) {
|
||||||
|
return view == object;
|
||||||
|
}
|
||||||
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) {
|
@Override
|
||||||
// Return pressed with finished session - remove it.
|
public Object instantiateItem(ViewGroup collection, int position) {
|
||||||
currentSession.finishIfRunning();
|
LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this);
|
||||||
|
View layout;
|
||||||
int index = mTermService.removeTermSession(currentSession);
|
if (position == 0) {
|
||||||
mListViewAdapter.notifyDataSetChanged();
|
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
|
||||||
if (mTermService.getSessions().isEmpty()) {
|
|
||||||
// There are no sessions to show, so finish the activity.
|
|
||||||
finish();
|
|
||||||
} else {
|
} else {
|
||||||
if (index >= mTermService.getSessions().size()) {
|
layout = inflater.inflate(R.layout.extra_keys_right, collection, false);
|
||||||
index = mTermService.getSessions().size() - 1;
|
final EditText editText = (EditText) layout.findViewById(R.id.text_input);
|
||||||
|
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
|
TerminalSession session = getCurrentTermSession();
|
||||||
|
if (session != null) {
|
||||||
|
if (session.isRunning()) {
|
||||||
|
session.write(editText.getText().toString() + "\n");
|
||||||
|
} else {
|
||||||
|
removeFinishedSession(session);
|
||||||
}
|
}
|
||||||
switchToSession(mTermService.getSessions().get(index));
|
editText.setText("");
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else if (!(event.isCtrlPressed() && event.isShiftPressed())) {
|
|
||||||
// Only hook shortcuts with Ctrl+Shift down.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the unmodified code point:
|
|
||||||
int unicodeChar = event.getUnicodeChar(0);
|
|
||||||
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || unicodeChar == 'n'/* next */) {
|
|
||||||
int index = mTermService.getSessions().indexOf(currentSession);
|
|
||||||
if (++index >= mTermService.getSessions().size()) index = 0;
|
|
||||||
switchToSession(mTermService.getSessions().get(index));
|
|
||||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP || unicodeChar == 'p' /* previous */) {
|
|
||||||
int index = mTermService.getSessions().indexOf(currentSession);
|
|
||||||
if (--index < 0) index = mTermService.getSessions().size() - 1;
|
|
||||||
switchToSession(mTermService.getSessions().get(index));
|
|
||||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
|
|
||||||
getDrawer().openDrawer(Gravity.START);
|
|
||||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
|
|
||||||
getDrawer().closeDrawers();
|
|
||||||
} else if (unicodeChar == 'f'/* full screen */) {
|
|
||||||
toggleImmersive();
|
|
||||||
} else if (unicodeChar == 'm'/* menu */) {
|
|
||||||
mTerminalView.showContextMenu();
|
|
||||||
} else if (unicodeChar == 'r'/* rename */) {
|
|
||||||
renameSession(currentSession);
|
|
||||||
} else if (unicodeChar == 'c'/* create */) {
|
|
||||||
addNewSession(false, null);
|
|
||||||
} else if (unicodeChar == 'u' /* urls */) {
|
|
||||||
showUrlSelection();
|
|
||||||
} else if (unicodeChar == 'v') {
|
|
||||||
doPaste();
|
|
||||||
} else if (unicodeChar == '+' || event.getUnicodeChar(KeyEvent.META_SHIFT_ON) == '+') {
|
|
||||||
// We also check for the shifted char here since shift may be required to produce '+',
|
|
||||||
// see https://github.com/termux/termux-api/issues/2
|
|
||||||
changeFontSize(true);
|
|
||||||
} else if (unicodeChar == '-') {
|
|
||||||
changeFontSize(false);
|
|
||||||
} else if (unicodeChar >= '1' && unicodeChar <= '9') {
|
|
||||||
int num = unicodeChar - '1';
|
|
||||||
if (mTermService.getSessions().size() > num) switchToSession(mTermService.getSessions().get(num));
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
mTerminalView.setOnKeyListener(keyListener);
|
|
||||||
findViewById(R.id.left_drawer_list).setOnKeyListener(keyListener);
|
|
||||||
|
|
||||||
mTerminalView.setOnKeyListener(new TerminalKeyListener() {
|
|
||||||
@Override
|
|
||||||
public float onScale(float scale) {
|
|
||||||
if (scale < 0.9f || scale > 1.1f) {
|
|
||||||
boolean increase = scale > 1.f;
|
|
||||||
changeFontSize(increase);
|
|
||||||
return 1.0f;
|
|
||||||
}
|
}
|
||||||
return scale;
|
collection.addView(layout);
|
||||||
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLongPress(MotionEvent event) {
|
public void destroyItem(ViewGroup collection, int position, Object view) {
|
||||||
mTerminalView.showContextMenu();
|
collection.removeView((View) view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleTapUp(MotionEvent e) {
|
|
||||||
// Toggle keyboard visibility if tapping with a finger:
|
|
||||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
findViewById(R.id.new_session_button).setOnClickListener(new OnClickListener() {
|
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onPageSelected(int position) {
|
||||||
|
if (position == 0) {
|
||||||
|
mTerminalView.requestFocus();
|
||||||
|
} else {
|
||||||
|
final EditText editText = (EditText) viewPager.findViewById(R.id.text_input);
|
||||||
|
if (editText != null) editText.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
View newSessionButton = findViewById(R.id.new_session_button);
|
||||||
|
newSessionButton.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
addNewSession(false, null);
|
addNewSession(false, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
newSessionButton.setOnLongClickListener(new OnLongClickListener() {
|
||||||
findViewById(R.id.new_session_button).setOnLongClickListener(new OnLongClickListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onLongClick(View v) {
|
public boolean onLongClick(View v) {
|
||||||
Resources res = getResources();
|
DialogUtils.textInput(TermuxActivity.this, R.string.session_new_named_title, null, R.string.session_new_named_positive_button,
|
||||||
new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.new_session)
|
|
||||||
.setItems(new String[] { res.getString(R.string.new_session_normal_unnamed), res.getString(R.string.new_session_normal_named),
|
|
||||||
res.getString(R.string.new_session_failsafe) }, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
switch (which) {
|
|
||||||
case 0:
|
|
||||||
addNewSession(false, null);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
DialogUtils.textInput(TermuxActivity.this, R.string.session_new_named_title, R.string.session_new_named_positive_button, null,
|
|
||||||
new DialogUtils.TextSetListener() {
|
new DialogUtils.TextSetListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTextSet(String text) {
|
public void onTextSet(String text) {
|
||||||
addNewSession(false, text);
|
addNewSession(false, text);
|
||||||
}
|
}
|
||||||
});
|
}, R.string.new_session_failsafe, new DialogUtils.TextSetListener() {
|
||||||
break;
|
@Override
|
||||||
case 2:
|
public void onTextSet(String text) {
|
||||||
addNewSession(true, null);
|
addNewSession(true, text);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).show();
|
, -1, null, null);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -280,15 +316,35 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(new OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
toggleShowExtraKeys();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
registerForContextMenu(mTerminalView);
|
registerForContextMenu(mTerminalView);
|
||||||
|
|
||||||
Intent serviceIntent = new Intent(this, TermuxService.class);
|
Intent serviceIntent = new Intent(this, TermuxService.class);
|
||||||
// Start the service and make it run regardless of who is bound to it:
|
// Start the service and make it run regardless of who is bound to it:
|
||||||
startService(serviceIntent);
|
startService(serviceIntent);
|
||||||
if (!bindService(serviceIntent, this, 0)) throw new RuntimeException("bindService() failed");
|
if (!bindService(serviceIntent, this, 0))
|
||||||
|
throw new RuntimeException("bindService() failed");
|
||||||
|
|
||||||
mTerminalView.checkForTypeface();
|
checkForFontAndColors();
|
||||||
mTerminalView.checkForColors();
|
|
||||||
|
mBellSoundId = mBellSoundPool.load(this, R.raw.bell, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleShowExtraKeys() {
|
||||||
|
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
|
||||||
|
final boolean showNow = mSettings.toggleShowExtraKeys(TermuxActivity.this);
|
||||||
|
viewPager.setVisibility(showNow ? View.VISIBLE : View.GONE);
|
||||||
|
if (showNow && viewPager.getCurrentItem() == 1) {
|
||||||
|
// Focus the text input view if just revealed.
|
||||||
|
findViewById(R.id.text_input).requestFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -330,7 +386,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
// Show toast for non-current sessions that exit.
|
// Show toast for non-current sessions that exit.
|
||||||
int indexOfSession = mTermService.getSessions().indexOf(finishedSession);
|
int indexOfSession = mTermService.getSessions().indexOf(finishedSession);
|
||||||
// Verify that session was not removed before we got told about it finishing:
|
// Verify that session was not removed before we got told about it finishing:
|
||||||
if (indexOfSession >= 0) showToast(toToastTitle(finishedSession) + " - exited", true);
|
if (indexOfSession >= 0)
|
||||||
|
showToast(toToastTitle(finishedSession) + " - exited", true);
|
||||||
}
|
}
|
||||||
mListViewAdapter.notifyDataSetChanged();
|
mListViewAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
@@ -338,14 +395,32 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
@Override
|
@Override
|
||||||
public void onClipboardText(TerminalSession session, String text) {
|
public void onClipboardText(TerminalSession session, String text) {
|
||||||
if (!mIsVisible) return;
|
if (!mIsVisible) return;
|
||||||
showToast("Clipboard set:\n\"" + text + "\"", true);
|
showToast("Clipboard:\n\"" + text + "\"", false);
|
||||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
clipboard.setPrimaryClip(new ClipData(null, new String[] { "text/plain" }, new ClipData.Item(text)));
|
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBell(TerminalSession session) {
|
public void onBell(TerminalSession session) {
|
||||||
if (mIsVisible) ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
|
if (mIsVisible) {
|
||||||
|
switch (mSettings.mBellBehaviour) {
|
||||||
|
case TermuxPreferences.BELL_BEEP:
|
||||||
|
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
||||||
|
break;
|
||||||
|
case TermuxPreferences.BELL_VIBRATE:
|
||||||
|
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
|
||||||
|
break;
|
||||||
|
case TermuxPreferences.BELL_IGNORE:
|
||||||
|
// Ignore the bell character.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onColorsChanged(TerminalSession changedSession) {
|
||||||
|
if (getCurrentTermSession() == changedSession) updateBackgroundColor();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -414,6 +489,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
TermuxInstaller.setupIfNeeded(TermuxActivity.this, new Runnable() {
|
TermuxInstaller.setupIfNeeded(TermuxActivity.this, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
if (mTermService == null) return; // Activity might have been destroyed.
|
||||||
|
try {
|
||||||
if (TermuxPreferences.isShowWelcomeDialog(TermuxActivity.this)) {
|
if (TermuxPreferences.isShowWelcomeDialog(TermuxActivity.this)) {
|
||||||
new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.welcome_dialog_title).setMessage(R.string.welcome_dialog_body)
|
new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.welcome_dialog_title).setMessage(R.string.welcome_dialog_body)
|
||||||
.setCancelable(false).setPositiveButton(android.R.string.ok, null)
|
.setCancelable(false).setPositiveButton(android.R.string.ok, null)
|
||||||
@@ -426,6 +503,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
addNewSession(false, null);
|
addNewSession(false, null);
|
||||||
|
} catch (WindowManager.BadTokenException e) {
|
||||||
|
// Activity finished - ignore.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -437,15 +517,25 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void switchToSession(boolean forward) {
|
||||||
|
TerminalSession currentSession = getCurrentTermSession();
|
||||||
|
int index = mTermService.getSessions().indexOf(currentSession);
|
||||||
|
if (forward) {
|
||||||
|
if (++index >= mTermService.getSessions().size()) index = 0;
|
||||||
|
} else {
|
||||||
|
if (--index < 0) index = mTermService.getSessions().size() - 1;
|
||||||
|
}
|
||||||
|
switchToSession(mTermService.getSessions().get(index));
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
void renameSession(final TerminalSession sessionToRename) {
|
void renameSession(final TerminalSession sessionToRename) {
|
||||||
DialogUtils.textInput(this, R.string.session_rename_title, R.string.session_rename_positive_button, sessionToRename.mSessionName,
|
DialogUtils.textInput(this, R.string.session_rename_title, sessionToRename.mSessionName, R.string.session_rename_positive_button, new DialogUtils.TextSetListener() {
|
||||||
new DialogUtils.TextSetListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextSet(String text) {
|
public void onTextSet(String text) {
|
||||||
sessionToRename.mSessionName = text;
|
sessionToRename.mSessionName = text;
|
||||||
}
|
}
|
||||||
});
|
}, -1, null, -1, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -456,6 +546,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
TerminalSession getCurrentTermSession() {
|
TerminalSession getCurrentTermSession() {
|
||||||
return mTerminalView.getCurrentSession();
|
return mTerminalView.getCurrentSession();
|
||||||
}
|
}
|
||||||
@@ -472,6 +563,10 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerReceiver(mBroadcastReceiever, new IntentFilter(RELOAD_STYLE_ACTION));
|
registerReceiver(mBroadcastReceiever, new IntentFilter(RELOAD_STYLE_ACTION));
|
||||||
|
|
||||||
|
// The current terminal session may have changed while being away, force
|
||||||
|
// a refresh of the displayed terminal:
|
||||||
|
mTerminalView.onScreenUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -486,11 +581,12 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (getDrawer().isDrawerOpen(Gravity.START))
|
if (getDrawer().isDrawerOpen(Gravity.LEFT)) {
|
||||||
getDrawer().closeDrawers();
|
getDrawer().closeDrawers();
|
||||||
else
|
} else {
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
@@ -524,7 +620,10 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
/** Try switching to session and note about it, but do nothing if already displaying the session. */
|
/** Try switching to session and note about it, but do nothing if already displaying the session. */
|
||||||
void switchToSession(TerminalSession session) {
|
void switchToSession(TerminalSession session) {
|
||||||
if (mTerminalView.attachSession(session)) noteSessionInfo();
|
if (mTerminalView.attachSession(session)) {
|
||||||
|
noteSessionInfo();
|
||||||
|
updateBackgroundColor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String toToastTitle(TerminalSession session) {
|
String toToastTitle(TerminalSession session) {
|
||||||
@@ -558,11 +657,10 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
TerminalSession currentSession = getCurrentTermSession();
|
TerminalSession currentSession = getCurrentTermSession();
|
||||||
if (currentSession == null) return;
|
if (currentSession == null) return;
|
||||||
|
|
||||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
menu.add(Menu.NONE, CONTEXTMENU_SELECT_URL_ID, Menu.NONE, R.string.select_url);
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_PASTE_ID, Menu.NONE, R.string.paste_text).setEnabled(clipboard.hasPrimaryClip());
|
menu.add(Menu.NONE, CONTEXTMENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.select_all_and_share);
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_SELECT_ID, Menu.NONE, R.string.select);
|
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
|
menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, R.string.kill_process).setEnabled(currentSession.isRunning());
|
menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.kill_process, getCurrentTermSession().getPid())).setEnabled(currentSession.isRunning());
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_FULLSCREEN_ID, Menu.NONE, R.string.toggle_fullscreen).setCheckable(true).setChecked(mSettings.isFullScreen());
|
menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_FULLSCREEN_ID, Menu.NONE, R.string.toggle_fullscreen).setCheckable(true).setChecked(mSettings.isFullScreen());
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal);
|
menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal);
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help);
|
menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help);
|
||||||
@@ -575,12 +673,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void showUrlSelection() {
|
static LinkedHashSet<CharSequence> extractUrls(String text) {
|
||||||
String text = getCurrentTermSession().getEmulator().getScreen().getTranscriptText();
|
|
||||||
// Pattern for recognizing a URL, based off RFC 3986
|
// Pattern for recognizing a URL, based off RFC 3986
|
||||||
// http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string
|
// http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string
|
||||||
final Pattern urlPattern = Pattern.compile(
|
final Pattern urlPattern = Pattern.compile(
|
||||||
"(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" + "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*" + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)",
|
"(?:^|[\\W])((ht|f)tp(s?)://|www\\.)" + "(([\\w\\-]+\\.)+?([\\w\\-.~]+/?)*" + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)",
|
||||||
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
||||||
LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>();
|
LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>();
|
||||||
Matcher matcher = urlPattern.matcher(text);
|
Matcher matcher = urlPattern.matcher(text);
|
||||||
@@ -590,7 +687,12 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
String url = text.substring(matchStart, matchEnd);
|
String url = text.substring(matchStart, matchEnd);
|
||||||
urlSet.add(url);
|
urlSet.add(url);
|
||||||
}
|
}
|
||||||
|
return urlSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showUrlSelection() {
|
||||||
|
String text = getCurrentTermSession().getEmulator().getScreen().getTranscriptText();
|
||||||
|
LinkedHashSet<CharSequence> urlSet = extractUrls(text);
|
||||||
if (urlSet.isEmpty()) {
|
if (urlSet.isEmpty()) {
|
||||||
new AlertDialog.Builder(this).setMessage(R.string.select_url_no_found).show();
|
new AlertDialog.Builder(this).setMessage(R.string.select_url_no_found).show();
|
||||||
return;
|
return;
|
||||||
@@ -605,7 +707,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
public void onClick(DialogInterface di, int which) {
|
public void onClick(DialogInterface di, int which) {
|
||||||
String url = (String) urls[which];
|
String url = (String) urls[which];
|
||||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
clipboard.setPrimaryClip(new ClipData(null, new String[] { "text/plain" }, new ClipData.Item(url)));
|
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(url)));
|
||||||
Toast.makeText(TermuxActivity.this, R.string.select_url_copied_to_clipboard, Toast.LENGTH_LONG).show();
|
Toast.makeText(TermuxActivity.this, R.string.select_url_copied_to_clipboard, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}).setTitle(R.string.select_url_dialog_title).create();
|
}).setTitle(R.string.select_url_dialog_title).create();
|
||||||
@@ -620,7 +722,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
String url = (String) urls[position];
|
String url = (String) urls[position];
|
||||||
startActivity(Intent.createChooser(new Intent(Intent.ACTION_VIEW, Uri.parse(url)), null));
|
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||||
|
try {
|
||||||
|
startActivity(i, null);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
// If no applications match, Android displays a system message.
|
||||||
|
startActivity(Intent.createChooser(i, null));
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -632,22 +740,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onContextItemSelected(MenuItem item) {
|
public boolean onContextItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
|
||||||
case CONTEXTMENU_SELECT_ID:
|
|
||||||
CharSequence[] items = new CharSequence[] { getString(R.string.select_text), getString(R.string.select_url),
|
|
||||||
getString(R.string.select_all_and_share) };
|
|
||||||
new AlertDialog.Builder(this).setItems(items, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
switch (which) {
|
|
||||||
case 0:
|
|
||||||
mTerminalView.toggleSelectingText();
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
showUrlSelection();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
TerminalSession session = getCurrentTermSession();
|
TerminalSession session = getCurrentTermSession();
|
||||||
|
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case CONTEXTMENU_SELECT_URL_ID:
|
||||||
|
showUrlSelection();
|
||||||
|
return true;
|
||||||
|
case CONTEXTMENU_SHARE_TRANSCRIPT_ID:
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
intent.setType("text/plain");
|
intent.setType("text/plain");
|
||||||
@@ -655,11 +754,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_transcript_title));
|
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_transcript_title));
|
||||||
startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title)));
|
startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title)));
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
return true;
|
return true;
|
||||||
case CONTEXTMENU_PASTE_ID:
|
case CONTEXTMENU_PASTE_ID:
|
||||||
doPaste();
|
doPaste();
|
||||||
@@ -679,7 +773,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
b.show();
|
b.show();
|
||||||
return true;
|
return true;
|
||||||
case CONTEXTMENU_RESET_TERMINAL_ID: {
|
case CONTEXTMENU_RESET_TERMINAL_ID: {
|
||||||
TerminalSession session = getCurrentTermSession();
|
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
session.reset();
|
session.reset();
|
||||||
showToast(getResources().getString(R.string.reset_toast_notification), true);
|
showToast(getResources().getString(R.string.reset_toast_notification), true);
|
||||||
@@ -691,9 +784,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
stylingIntent.setClassName("com.termux.styling", "com.termux.styling.TermuxStyleActivity");
|
stylingIntent.setClassName("com.termux.styling", "com.termux.styling.TermuxStyleActivity");
|
||||||
try {
|
try {
|
||||||
startActivity(stylingIntent);
|
startActivity(stylingIntent);
|
||||||
} catch (ActivityNotFoundException e) {
|
} catch (ActivityNotFoundException | IllegalArgumentException e) {
|
||||||
|
// The startActivity() call is not documented to throw IllegalArgumentException.
|
||||||
|
// However, crash reporting shows that it sometimes does, so catch it here.
|
||||||
new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed)
|
new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed)
|
||||||
.setPositiveButton(R.string.styling_install, new android.content.DialogInterface.OnClickListener() {
|
.setPositiveButton(R.string.styling_install, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.termux.styling")));
|
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.termux.styling")));
|
||||||
@@ -713,6 +808,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
|
||||||
|
if (requestCode == REQUESTCODE_PERMISSION_STORAGE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
TermuxInstaller.setupStorageSymlinks(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void toggleImmersive() {
|
void toggleImmersive() {
|
||||||
boolean newValue = !mSettings.isFullScreen();
|
boolean newValue = !mSettings.isFullScreen();
|
||||||
mSettings.setFullScreen(this, newValue);
|
mSettings.setFullScreen(this, newValue);
|
||||||
@@ -729,7 +831,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
ClipData clipData = clipboard.getPrimaryClip();
|
ClipData clipData = clipboard.getPrimaryClip();
|
||||||
if (clipData == null) return;
|
if (clipData == null) return;
|
||||||
CharSequence paste = clipData.getItemAt(0).coerceToText(this);
|
CharSequence paste = clipData.getItemAt(0).coerceToText(this);
|
||||||
if (!TextUtils.isEmpty(paste)) getCurrentTermSession().getEmulator().paste(paste.toString());
|
if (!TextUtils.isEmpty(paste))
|
||||||
|
getCurrentTermSession().getEmulator().paste(paste.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The current session as stored or the last one if that does not exist. */
|
/** The current session as stored or the last one if that does not exist. */
|
||||||
@@ -749,4 +852,21 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
mLastToast.show();
|
mLastToast.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeFinishedSession(TerminalSession finishedSession) {
|
||||||
|
// Return pressed with finished session - remove it.
|
||||||
|
TermuxService service = mTermService;
|
||||||
|
|
||||||
|
int index = service.removeTermSession(finishedSession);
|
||||||
|
mListViewAdapter.notifyDataSetChanged();
|
||||||
|
if (mTermService.getSessions().isEmpty()) {
|
||||||
|
// There are no sessions to show, so finish the activity.
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
if (index >= service.getSessions().size()) {
|
||||||
|
index = service.getSessions().size() - 1;
|
||||||
|
}
|
||||||
|
switchToSession(service.getSessions().get(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,33 +5,62 @@ import android.content.ActivityNotFoundException;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
/** Basic embedded browser for viewing the bundled help page. */
|
/** Basic embedded browser for viewing help pages. */
|
||||||
public final class TermuxHelpActivity extends Activity {
|
public final class TermuxHelpActivity extends Activity {
|
||||||
|
|
||||||
private WebView mWebView;
|
WebView mWebView;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
final RelativeLayout progressLayout = new RelativeLayout(this);
|
||||||
|
RelativeLayout.LayoutParams lParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
lParams.addRule(RelativeLayout.CENTER_IN_PARENT);
|
||||||
|
ProgressBar progressBar = new ProgressBar(this);
|
||||||
|
progressBar.setIndeterminate(true);
|
||||||
|
progressBar.setLayoutParams(lParams);
|
||||||
|
progressLayout.addView(progressBar);
|
||||||
|
|
||||||
mWebView = new WebView(this);
|
mWebView = new WebView(this);
|
||||||
setContentView(mWebView);
|
WebSettings settings = mWebView.getSettings();
|
||||||
|
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
|
||||||
|
settings.setAppCacheEnabled(false);
|
||||||
|
setContentView(progressLayout);
|
||||||
|
mWebView.clearCache(true);
|
||||||
|
|
||||||
mWebView.setWebViewClient(new WebViewClient() {
|
mWebView.setWebViewClient(new WebViewClient() {
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
|
if (url.startsWith("https://termux.com")) {
|
||||||
|
// Inline help.
|
||||||
|
setContentView(progressLayout);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||||
} catch (ActivityNotFoundException e) {
|
} catch (ActivityNotFoundException e) {
|
||||||
// TODO: Android TV does not have a system browser - but needs better method of getting back
|
// Android TV does not have a system browser.
|
||||||
// than navigating deep here.
|
setContentView(progressLayout);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView view, String url) {
|
||||||
|
setContentView(mWebView);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
mWebView.loadUrl("file:///android_asset/help.html");
|
mWebView.loadUrl("https://termux.com/help.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
package com.termux.app;
|
package com.termux.app;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
@@ -18,30 +7,46 @@ import android.content.Context;
|
|||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.DialogInterface.OnClickListener;
|
import android.content.DialogInterface.OnClickListener;
|
||||||
import android.content.DialogInterface.OnDismissListener;
|
import android.content.DialogInterface.OnDismissListener;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.UserManager;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import com.termux.R;
|
import com.termux.R;
|
||||||
import com.termux.terminal.EmulatorDebug;
|
import com.termux.terminal.EmulatorDebug;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install the Termux bootstrap packages if necessary by following the below steps:
|
* Install the Termux bootstrap packages if necessary by following the below steps:
|
||||||
*
|
* <p/>
|
||||||
* (1) If $PREFIX already exist, assume that it is correct and be done. Note that this relies on that we do not create a
|
* (1) If $PREFIX already exist, assume that it is correct and be done. Note that this relies on that we do not create a
|
||||||
* broken $PREFIX folder below.
|
* broken $PREFIX folder below.
|
||||||
*
|
* <p/>
|
||||||
* (2) A progress dialog is shown with "Installing..." message and a spinner.
|
* (2) A progress dialog is shown with "Installing..." message and a spinner.
|
||||||
*
|
* <p/>
|
||||||
* (3) A staging folder, $STAGING_PREFIX, is {@link #deleteFolder(File)} if left over from broken installation below.
|
* (3) A staging folder, $STAGING_PREFIX, is {@link #deleteFolder(File)} if left over from broken installation below.
|
||||||
*
|
* <p/>
|
||||||
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}.
|
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}.
|
||||||
*
|
* <p/>
|
||||||
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
|
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
|
||||||
* continously encountering zip file entries:
|
* continously encountering zip file entries:
|
||||||
*
|
* <p/>
|
||||||
* (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup.
|
* (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup.
|
||||||
*
|
* <p/>
|
||||||
* (5.2) For every other zip entry, extract it into $STAGING_PREFIX and set execute permissions if necessary.
|
* (5.2) For every other zip entry, extract it into $STAGING_PREFIX and set execute permissions if necessary.
|
||||||
*/
|
*/
|
||||||
final class TermuxInstaller {
|
final class TermuxInstaller {
|
||||||
@@ -50,7 +55,7 @@ final class TermuxInstaller {
|
|||||||
static void setupIfNeeded(final Activity activity, final Runnable whenDone) {
|
static void setupIfNeeded(final Activity activity, final Runnable whenDone) {
|
||||||
// Termux can only be run as the primary user (device owner) since only that
|
// Termux can only be run as the primary user (device owner) since only that
|
||||||
// account has the expected file system paths. Verify that:
|
// account has the expected file system paths. Verify that:
|
||||||
android.os.UserManager um = (android.os.UserManager) activity.getSystemService(Context.USER_SERVICE);
|
UserManager um = (UserManager) activity.getSystemService(Context.USER_SERVICE);
|
||||||
boolean isPrimaryUser = um.getSerialNumberForUser(android.os.Process.myUserHandle()) == 0;
|
boolean isPrimaryUser = um.getSerialNumberForUser(android.os.Process.myUserHandle()) == 0;
|
||||||
if (!isPrimaryUser) {
|
if (!isPrimaryUser) {
|
||||||
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_not_primary_user_message)
|
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_not_primary_user_message)
|
||||||
@@ -93,7 +98,8 @@ final class TermuxInstaller {
|
|||||||
String line;
|
String line;
|
||||||
while ((line = symlinksReader.readLine()) != null) {
|
while ((line = symlinksReader.readLine()) != null) {
|
||||||
String[] parts = line.split("←");
|
String[] parts = line.split("←");
|
||||||
if (parts.length != 2) throw new RuntimeException("Malformed symlink line: " + line);
|
if (parts.length != 2)
|
||||||
|
throw new RuntimeException("Malformed symlink line: " + line);
|
||||||
String oldPath = parts[0];
|
String oldPath = parts[0];
|
||||||
String newPath = STAGING_PREFIX_PATH + "/" + parts[1];
|
String newPath = STAGING_PREFIX_PATH + "/" + parts[1];
|
||||||
symlinks.add(Pair.create(oldPath, newPath));
|
symlinks.add(Pair.create(oldPath, newPath));
|
||||||
@@ -102,7 +108,8 @@ final class TermuxInstaller {
|
|||||||
String zipEntryName = zipEntry.getName();
|
String zipEntryName = zipEntry.getName();
|
||||||
File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName);
|
File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName);
|
||||||
if (zipEntry.isDirectory()) {
|
if (zipEntry.isDirectory()) {
|
||||||
if (!targetFile.mkdirs()) throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath());
|
if (!targetFile.mkdirs())
|
||||||
|
throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath());
|
||||||
} else {
|
} else {
|
||||||
try (FileOutputStream outStream = new FileOutputStream(targetFile)) {
|
try (FileOutputStream outStream = new FileOutputStream(targetFile)) {
|
||||||
int readBytes;
|
int readBytes;
|
||||||
@@ -118,7 +125,8 @@ final class TermuxInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (symlinks.isEmpty()) throw new RuntimeException("No SYMLINKS.txt encountered");
|
if (symlinks.isEmpty())
|
||||||
|
throw new RuntimeException("No SYMLINKS.txt encountered");
|
||||||
for (Pair<String, String> symlink : symlinks) {
|
for (Pair<String, String> symlink : symlinks) {
|
||||||
Os.symlink(symlink.first, symlink.second);
|
Os.symlink(symlink.first, symlink.second);
|
||||||
}
|
}
|
||||||
@@ -138,6 +146,7 @@ final class TermuxInstaller {
|
|||||||
activity.runOnUiThread(new Runnable() {
|
activity.runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
try {
|
||||||
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
|
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
|
||||||
.setNegativeButton(R.string.bootstrap_error_abort, new OnClickListener() {
|
.setNegativeButton(R.string.bootstrap_error_abort, new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -152,13 +161,20 @@ final class TermuxInstaller {
|
|||||||
TermuxInstaller.setupIfNeeded(activity, whenDone);
|
TermuxInstaller.setupIfNeeded(activity, whenDone);
|
||||||
}
|
}
|
||||||
}).show();
|
}).show();
|
||||||
|
} catch (WindowManager.BadTokenException e) {
|
||||||
|
// Activity already dismissed - ignore.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
activity.runOnUiThread(new Runnable() {
|
activity.runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
try {
|
||||||
progress.dismiss();
|
progress.dismiss();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// Activity already dismissed - ignore.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -168,14 +184,28 @@ final class TermuxInstaller {
|
|||||||
|
|
||||||
/** Get bootstrap zip url for this systems cpu architecture. */
|
/** Get bootstrap zip url for this systems cpu architecture. */
|
||||||
static URL determineZipUrl() throws MalformedURLException {
|
static URL determineZipUrl() throws MalformedURLException {
|
||||||
String arch = System.getProperty("os.arch");
|
String archName = determineTermuxArchName();
|
||||||
if (arch.startsWith("arm") || arch.equals("aarch64")) {
|
return new URL("https://termux.net/bootstrap/bootstrap-" + archName + ".zip");
|
||||||
// Handle different arm variants such as armv7l:
|
|
||||||
arch = "arm";
|
|
||||||
} else if (arch.equals("x86_64")) {
|
|
||||||
arch = "i686";
|
|
||||||
}
|
}
|
||||||
return new URL("http://apt.termux.com/bootstrap/bootstrap-" + arch + ".zip");
|
|
||||||
|
private static String determineTermuxArchName() {
|
||||||
|
// Note that we cannot use System.getProperty("os.arch") since that may give e.g. "aarch64"
|
||||||
|
// while a 64-bit runtime may not be installed (like on the Samsung Galaxy S5 Neo).
|
||||||
|
// Instead we search through the supported abi:s on the device, see:
|
||||||
|
// http://developer.android.com/ndk/guides/abis.html
|
||||||
|
// Note that we search for abi:s in preferred order (the ordering of the
|
||||||
|
// Build.SUPPORTED_ABIS list) to avoid e.g. installing arm on an x86 system where arm
|
||||||
|
// emulation is available.
|
||||||
|
for (String androidArch : Build.SUPPORTED_ABIS) {
|
||||||
|
switch (androidArch) {
|
||||||
|
case "arm64-v8a": return "aarch64";
|
||||||
|
case "armeabi-v7a": return "arm";
|
||||||
|
case "x86_64": return "x86_64";
|
||||||
|
case "x86": return "i686";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Unable to determine arch from Build.SUPPORTED_ABIS = " +
|
||||||
|
Arrays.toString(Build.SUPPORTED_ABIS));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Delete a folder and all its content or throw. */
|
/** Delete a folder and all its content or throw. */
|
||||||
@@ -191,4 +221,51 @@ final class TermuxInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setupStorageSymlinks(final Context context) {
|
||||||
|
final String LOG_TAG = "termux-storage";
|
||||||
|
new Thread() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
File storageDir = new File(TermuxService.HOME_PATH, "storage");
|
||||||
|
|
||||||
|
if (storageDir.exists() && !storageDir.delete()) {
|
||||||
|
Log.e(LOG_TAG, "Could not delete old $HOME/storage");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!storageDir.mkdirs()) {
|
||||||
|
Log.e(LOG_TAG, "Unable to mkdirs() for $HOME/storage");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File sharedDir = Environment.getExternalStorageDirectory();
|
||||||
|
Os.symlink(sharedDir.getAbsolutePath(), new File(storageDir, "shared").getAbsolutePath());
|
||||||
|
|
||||||
|
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||||
|
Os.symlink(downloadsDir.getAbsolutePath(), new File(storageDir, "downloads").getAbsolutePath());
|
||||||
|
|
||||||
|
File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
|
||||||
|
Os.symlink(dcimDir.getAbsolutePath(), new File(storageDir, "dcim").getAbsolutePath());
|
||||||
|
|
||||||
|
File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
||||||
|
Os.symlink(picturesDir.getAbsolutePath(), new File(storageDir, "pictures").getAbsolutePath());
|
||||||
|
|
||||||
|
File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
|
||||||
|
Os.symlink(musicDir.getAbsolutePath(), new File(storageDir, "music").getAbsolutePath());
|
||||||
|
|
||||||
|
File moviesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
|
||||||
|
Os.symlink(moviesDir.getAbsolutePath(), new File(storageDir, "movies").getAbsolutePath());
|
||||||
|
|
||||||
|
final File[] dirs = context.getExternalFilesDirs(null);
|
||||||
|
if (dirs != null && dirs.length >= 2) {
|
||||||
|
final File externalDir = dirs[1];
|
||||||
|
Os.symlink(externalDir.getAbsolutePath(), new File(storageDir, "external").getAbsolutePath());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(LOG_TAG, "Error setting up link", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
276
app/src/main/java/com/termux/app/TermuxKeyListener.java
Normal file
276
app/src/main/java/com/termux/app/TermuxKeyListener.java
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
package com.termux.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.support.v4.widget.DrawerLayout;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
|
import com.termux.terminal.KeyHandler;
|
||||||
|
import com.termux.terminal.TerminalEmulator;
|
||||||
|
import com.termux.terminal.TerminalSession;
|
||||||
|
import com.termux.view.TerminalKeyListener;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class TermuxKeyListener implements TerminalKeyListener {
|
||||||
|
|
||||||
|
final TermuxActivity mActivity;
|
||||||
|
|
||||||
|
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
|
||||||
|
boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
|
||||||
|
|
||||||
|
public TermuxKeyListener(TermuxActivity activity) {
|
||||||
|
this.mActivity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float onScale(float scale) {
|
||||||
|
if (scale < 0.9f || scale > 1.1f) {
|
||||||
|
boolean increase = scale > 1.f;
|
||||||
|
mActivity.changeFontSize(increase);
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSingleTapUp(MotionEvent e) {
|
||||||
|
InputMethodManager mgr = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
mgr.showSoftInput(mActivity.mTerminalView, InputMethodManager.SHOW_IMPLICIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldBackButtonBeMappedToEscape() {
|
||||||
|
return mActivity.mSettings.mBackIsEscape;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void copyModeChanged(boolean copyMode) {
|
||||||
|
// Disable drawer while copying.
|
||||||
|
mActivity.getDrawer().setDrawerLockMode(copyMode ? DrawerLayout.LOCK_MODE_LOCKED_CLOSED : DrawerLayout.LOCK_MODE_UNLOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession currentSession) {
|
||||||
|
if (handleVirtualKeys(keyCode, e, true)) return true;
|
||||||
|
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) {
|
||||||
|
mActivity.removeFinishedSession(currentSession);
|
||||||
|
return true;
|
||||||
|
} else if (e.isCtrlPressed() && e.isAltPressed()) {
|
||||||
|
// Get the unmodified code point:
|
||||||
|
int unicodeChar = e.getUnicodeChar(0);
|
||||||
|
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || unicodeChar == 'n'/* next */) {
|
||||||
|
mActivity.switchToSession(true);
|
||||||
|
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP || unicodeChar == 'p' /* previous */) {
|
||||||
|
mActivity.switchToSession(false);
|
||||||
|
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
|
||||||
|
mActivity.getDrawer().openDrawer(Gravity.LEFT);
|
||||||
|
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
|
||||||
|
mActivity.getDrawer().closeDrawers();
|
||||||
|
} else if (unicodeChar == 'f'/* full screen */) {
|
||||||
|
mActivity.toggleImmersive();
|
||||||
|
} else if (unicodeChar == 'k'/* keyboard */) {
|
||||||
|
InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||||
|
} else if (unicodeChar == 'm'/* menu */) {
|
||||||
|
mActivity.mTerminalView.showContextMenu();
|
||||||
|
} else if (unicodeChar == 'r'/* rename */) {
|
||||||
|
mActivity.renameSession(currentSession);
|
||||||
|
} else if (unicodeChar == 'c'/* create */) {
|
||||||
|
mActivity.addNewSession(false, null);
|
||||||
|
} else if (unicodeChar == 'u' /* urls */) {
|
||||||
|
mActivity.showUrlSelection();
|
||||||
|
} else if (unicodeChar == 'v') {
|
||||||
|
mActivity.doPaste();
|
||||||
|
} else if (unicodeChar == '+' || e.getUnicodeChar(KeyEvent.META_SHIFT_ON) == '+') {
|
||||||
|
// We also check for the shifted char here since shift may be required to produce '+',
|
||||||
|
// see https://github.com/termux/termux-api/issues/2
|
||||||
|
mActivity.changeFontSize(true);
|
||||||
|
} else if (unicodeChar == '-') {
|
||||||
|
mActivity.changeFontSize(false);
|
||||||
|
} else if (unicodeChar >= '1' && unicodeChar <= '9') {
|
||||||
|
int num = unicodeChar - '1';
|
||||||
|
TermuxService service = mActivity.mTermService;
|
||||||
|
if (service.getSessions().size() > num)
|
||||||
|
mActivity.switchToSession(service.getSessions().get(num));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyUp(int keyCode, KeyEvent e) {
|
||||||
|
return handleVirtualKeys(keyCode, e, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readControlKey() {
|
||||||
|
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readControlButton()) || mVirtualControlKeyDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readAltKey() {
|
||||||
|
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readAltButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCodePoint(final int codePoint, boolean ctrlDown, TerminalSession session) {
|
||||||
|
if (mVirtualFnKeyDown) {
|
||||||
|
int resultingKeyCode = -1;
|
||||||
|
int resultingCodePoint = -1;
|
||||||
|
boolean altDown = false;
|
||||||
|
int lowerCase = Character.toLowerCase(codePoint);
|
||||||
|
switch (lowerCase) {
|
||||||
|
// Arrow keys.
|
||||||
|
case 'w':
|
||||||
|
resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP;
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Page up and down.
|
||||||
|
case 'p':
|
||||||
|
resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP;
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Some special keys:
|
||||||
|
case 't':
|
||||||
|
resultingKeyCode = KeyEvent.KEYCODE_TAB;
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
resultingKeyCode = KeyEvent.KEYCODE_INSERT;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
resultingCodePoint = '~';
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Special characters to input.
|
||||||
|
case 'u':
|
||||||
|
resultingCodePoint = '_';
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
resultingCodePoint = '|';
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Function keys.
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
resultingKeyCode = (codePoint - '1') + KeyEvent.KEYCODE_F1;
|
||||||
|
break;
|
||||||
|
case '0':
|
||||||
|
resultingKeyCode = KeyEvent.KEYCODE_F10;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Other special keys.
|
||||||
|
case 'e':
|
||||||
|
resultingCodePoint = /*Escape*/ 27;
|
||||||
|
break;
|
||||||
|
case '.':
|
||||||
|
resultingCodePoint = /*^.*/ 28;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'b': // alt+b, jumping backward in readline.
|
||||||
|
case 'f': // alf+f, jumping forward in readline.
|
||||||
|
case 'x': // alt+x, common in emacs.
|
||||||
|
resultingCodePoint = lowerCase;
|
||||||
|
altDown = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Volume control.
|
||||||
|
case 'v':
|
||||||
|
resultingCodePoint = -1;
|
||||||
|
AudioManager audio = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Writing mode:
|
||||||
|
case 'q':
|
||||||
|
mActivity.toggleShowExtraKeys();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultingKeyCode != -1) {
|
||||||
|
TerminalEmulator term = session.getEmulator();
|
||||||
|
session.write(KeyHandler.getCode(resultingKeyCode, 0, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode()));
|
||||||
|
} else if (resultingCodePoint != -1) {
|
||||||
|
session.writeCodePoint(altDown, resultingCodePoint);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (ctrlDown) {
|
||||||
|
if (codePoint == 106 /* Ctrl+j or \n */ && !session.isRunning()) {
|
||||||
|
mActivity.removeFinishedSession(session);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TermuxPreferences.KeyboardShortcut> shortcuts = mActivity.mSettings.shortcuts;
|
||||||
|
if (!shortcuts.isEmpty()) {
|
||||||
|
int codePointLowerCase = Character.toLowerCase(codePoint);
|
||||||
|
for (int i = shortcuts.size() - 1; i >= 0; i--) {
|
||||||
|
TermuxPreferences.KeyboardShortcut shortcut = shortcuts.get(i);
|
||||||
|
if (codePointLowerCase == shortcut.codePoint) {
|
||||||
|
switch (shortcut.shortcutAction) {
|
||||||
|
case TermuxPreferences.SHORTCUT_ACTION_CREATE_SESSION:
|
||||||
|
mActivity.addNewSession(false, null);
|
||||||
|
return true;
|
||||||
|
case TermuxPreferences.SHORTCUT_ACTION_PREVIOUS_SESSION:
|
||||||
|
mActivity.switchToSession(false);
|
||||||
|
return true;
|
||||||
|
case TermuxPreferences.SHORTCUT_ACTION_NEXT_SESSION:
|
||||||
|
mActivity.switchToSession(true);
|
||||||
|
return true;
|
||||||
|
case TermuxPreferences.SHORTCUT_ACTION_RENAME_SESSION:
|
||||||
|
mActivity.renameSession(mActivity.getCurrentTermSession());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handle dedicated volume buttons as virtual keys if applicable. */
|
||||||
|
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
|
||||||
|
InputDevice inputDevice = event.getDevice();
|
||||||
|
if (inputDevice != null && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||||
|
// Do not steal dedicated buttons from a full external keyboard.
|
||||||
|
return false;
|
||||||
|
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||||
|
mVirtualControlKeyDown = down;
|
||||||
|
return true;
|
||||||
|
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||||
|
mVirtualFnKeyDown = down;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,17 +1,39 @@
|
|||||||
package com.termux.app;
|
package com.termux.app;
|
||||||
|
|
||||||
import com.termux.terminal.TerminalSession;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.termux.terminal.TerminalSession;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
final class TermuxPreferences {
|
final class TermuxPreferences {
|
||||||
|
|
||||||
|
@IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface AsciiBellBehaviour {
|
||||||
|
}
|
||||||
|
|
||||||
|
static final int BELL_VIBRATE = 1;
|
||||||
|
static final int BELL_BEEP = 2;
|
||||||
|
static final int BELL_IGNORE = 3;
|
||||||
|
|
||||||
private final int MIN_FONTSIZE;
|
private final int MIN_FONTSIZE;
|
||||||
private static final int MAX_FONTSIZE = 256;
|
private static final int MAX_FONTSIZE = 256;
|
||||||
|
|
||||||
private static final String FULLSCREEN_KEY = "fullscreen";
|
private static final String FULLSCREEN_KEY = "fullscreen";
|
||||||
|
private static final String SHOW_EXTRA_KEYS_KEY = "show_extra_keys";
|
||||||
private static final String FONTSIZE_KEY = "fontsize";
|
private static final String FONTSIZE_KEY = "fontsize";
|
||||||
private static final String CURRENT_SESSION_KEY = "current_session";
|
private static final String CURRENT_SESSION_KEY = "current_session";
|
||||||
private static final String SHOW_WELCOME_DIALOG_KEY = "intro_dialog";
|
private static final String SHOW_WELCOME_DIALOG_KEY = "intro_dialog";
|
||||||
@@ -19,7 +41,14 @@ final class TermuxPreferences {
|
|||||||
private boolean mFullScreen;
|
private boolean mFullScreen;
|
||||||
private int mFontSize;
|
private int mFontSize;
|
||||||
|
|
||||||
|
@AsciiBellBehaviour
|
||||||
|
int mBellBehaviour = BELL_VIBRATE;
|
||||||
|
|
||||||
|
boolean mBackIsEscape;
|
||||||
|
boolean mShowExtraKeys;
|
||||||
|
|
||||||
TermuxPreferences(Context context) {
|
TermuxPreferences(Context context) {
|
||||||
|
reloadFromProperties(context);
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
|
||||||
float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
|
float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
|
||||||
@@ -29,6 +58,7 @@ final class TermuxPreferences {
|
|||||||
MIN_FONTSIZE = (int) (4f * dipInPixels);
|
MIN_FONTSIZE = (int) (4f * dipInPixels);
|
||||||
|
|
||||||
mFullScreen = prefs.getBoolean(FULLSCREEN_KEY, false);
|
mFullScreen = prefs.getBoolean(FULLSCREEN_KEY, false);
|
||||||
|
mShowExtraKeys = prefs.getBoolean(SHOW_EXTRA_KEYS_KEY, false);
|
||||||
|
|
||||||
// http://www.google.com/design/spec/style/typography.html#typography-line-height
|
// http://www.google.com/design/spec/style/typography.html#typography-line-height
|
||||||
int defaultFontSize = Math.round(12 * dipInPixels);
|
int defaultFontSize = Math.round(12 * dipInPixels);
|
||||||
@@ -49,8 +79,17 @@ final class TermuxPreferences {
|
|||||||
|
|
||||||
void setFullScreen(Context context, boolean newValue) {
|
void setFullScreen(Context context, boolean newValue) {
|
||||||
mFullScreen = newValue;
|
mFullScreen = newValue;
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(FULLSCREEN_KEY, newValue).apply();
|
||||||
prefs.edit().putBoolean(FULLSCREEN_KEY, newValue).apply();
|
}
|
||||||
|
|
||||||
|
boolean isShowExtraKeys() {
|
||||||
|
return mShowExtraKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean toggleShowExtraKeys(Context context) {
|
||||||
|
mShowExtraKeys = !mShowExtraKeys;
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_EXTRA_KEYS_KEY, mShowExtraKeys).apply();
|
||||||
|
return mShowExtraKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getFontSize() {
|
int getFontSize() {
|
||||||
@@ -86,4 +125,83 @@ final class TermuxPreferences {
|
|||||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_WELCOME_DIALOG_KEY, false).apply();
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_WELCOME_DIALOG_KEY, false).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reloadFromProperties(Context context) {
|
||||||
|
try {
|
||||||
|
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
|
||||||
|
if (!propsFile.exists())
|
||||||
|
propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties");
|
||||||
|
|
||||||
|
Properties props = new Properties();
|
||||||
|
if (propsFile.isFile() && propsFile.canRead()) {
|
||||||
|
try (FileInputStream in = new FileInputStream(propsFile)) {
|
||||||
|
props.load(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (props.getProperty("bell-character", "vibrate")) {
|
||||||
|
case "beep":
|
||||||
|
mBellBehaviour = BELL_BEEP;
|
||||||
|
break;
|
||||||
|
case "ignore":
|
||||||
|
mBellBehaviour = BELL_IGNORE;
|
||||||
|
break;
|
||||||
|
default: // "vibrate".
|
||||||
|
mBellBehaviour = BELL_VIBRATE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mBackIsEscape = "escape".equals(props.getProperty("back-key", "back"));
|
||||||
|
|
||||||
|
shortcuts.clear();
|
||||||
|
parseAction("shortcut.create-session", SHORTCUT_ACTION_CREATE_SESSION, props);
|
||||||
|
parseAction("shortcut.next-session", SHORTCUT_ACTION_NEXT_SESSION, props);
|
||||||
|
parseAction("shortcut.previous-session", SHORTCUT_ACTION_PREVIOUS_SESSION, props);
|
||||||
|
parseAction("shortcut.rename-session", SHORTCUT_ACTION_RENAME_SESSION, props);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Toast.makeText(context, "Error loading properties: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
|
Log.e("termux", "Error loading props", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int SHORTCUT_ACTION_CREATE_SESSION = 1;
|
||||||
|
public static final int SHORTCUT_ACTION_NEXT_SESSION = 2;
|
||||||
|
public static final int SHORTCUT_ACTION_PREVIOUS_SESSION = 3;
|
||||||
|
public static final int SHORTCUT_ACTION_RENAME_SESSION = 4;
|
||||||
|
|
||||||
|
public final static class KeyboardShortcut {
|
||||||
|
|
||||||
|
public KeyboardShortcut(int codePoint, int shortcutAction) {
|
||||||
|
this.codePoint = codePoint;
|
||||||
|
this.shortcutAction = shortcutAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int codePoint;
|
||||||
|
final int shortcutAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<KeyboardShortcut> shortcuts = new ArrayList<>();
|
||||||
|
|
||||||
|
private void parseAction(String name, int shortcutAction, Properties props) {
|
||||||
|
String value = props.getProperty(name);
|
||||||
|
if (value == null) return;
|
||||||
|
String[] parts = value.toLowerCase().trim().split("\\+");
|
||||||
|
String input = parts.length == 2 ? parts[1].trim() : null;
|
||||||
|
if (!(parts.length == 2 && parts[0].trim().equals("ctrl")) || input.isEmpty() || input.length() > 2) {
|
||||||
|
Log.e("termux", "Keyboard shortcut '" + name + "' is not Ctrl+<something>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char c = input.charAt(0);
|
||||||
|
int codePoint = c;
|
||||||
|
if (Character.isLowSurrogate(c)) {
|
||||||
|
if (input.length() != 2 || Character.isHighSurrogate(input.charAt(1))) {
|
||||||
|
Log.e("termux", "Keyboard shortcut '" + name + "' is not Ctrl+<something>");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
codePoint = Character.toCodePoint(input.charAt(1), c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shortcuts.add(new KeyboardShortcut(codePoint, shortcutAction));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,5 @@
|
|||||||
package com.termux.app;
|
package com.termux.app;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.termux.R;
|
|
||||||
import com.termux.terminal.EmulatorDebug;
|
|
||||||
import com.termux.terminal.TerminalSession;
|
|
||||||
import com.termux.terminal.TerminalSession.SessionChangedCallback;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
@@ -26,15 +16,25 @@ import android.os.PowerManager;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
|
||||||
|
import com.termux.R;
|
||||||
|
import com.termux.terminal.EmulatorDebug;
|
||||||
|
import com.termux.terminal.TerminalSession;
|
||||||
|
import com.termux.terminal.TerminalSession.SessionChangedCallback;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A service holding a list of terminal sessions, {@link #mTerminalSessions}, showing a foreground notification while
|
* A service holding a list of terminal sessions, {@link #mTerminalSessions}, showing a foreground notification while
|
||||||
* running so that it is not terminated. The user interacts with the session through {@link TermuxActivity}, but this
|
* running so that it is not terminated. The user interacts with the session through {@link TermuxActivity}, but this
|
||||||
* service may outlive the activity when the user or the system disposes of the activity. In that case the user may
|
* service may outlive the activity when the user or the system disposes of the activity. In that case the user may
|
||||||
* restart {@link TermuxActivity} later to yet again access the sessions.
|
* restart {@link TermuxActivity} later to yet again access the sessions.
|
||||||
*
|
* <p/>
|
||||||
* In order to keep both terminal sessions and spawned processes (who may outlive the terminal sessions) alive as long
|
* In order to keep both terminal sessions and spawned processes (who may outlive the terminal sessions) alive as long
|
||||||
* as wanted by the user this service is a foreground service, {@link Service#startForeground(int, Notification)}.
|
* as wanted by the user this service is a foreground service, {@link Service#startForeground(int, Notification)}.
|
||||||
*
|
* <p/>
|
||||||
* Optionally may hold a wake and a wifi lock, in which case that is shown in the notification - see
|
* Optionally may hold a wake and a wifi lock, in which case that is shown in the notification - see
|
||||||
* {@link #buildNotification()}.
|
* {@link #buildNotification()}.
|
||||||
*/
|
*/
|
||||||
@@ -55,7 +55,11 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
/** Intent action to toggle the wifi lock, {@link #mWifiLock}, which this service may hold. */
|
/** Intent action to toggle the wifi lock, {@link #mWifiLock}, which this service may hold. */
|
||||||
private static final String ACTION_LOCK_WIFI = "com.termux.service_toggle_wifi_lock";
|
private static final String ACTION_LOCK_WIFI = "com.termux.service_toggle_wifi_lock";
|
||||||
/** Intent action to launch a new terminal session. Executed from TermuxWidgetProvider. */
|
/** Intent action to launch a new terminal session. Executed from TermuxWidgetProvider. */
|
||||||
private static final String ACTION_EXECUTE = "com.termux.service_execute";
|
public static final String ACTION_EXECUTE = "com.termux.service_execute";
|
||||||
|
|
||||||
|
public static final String EXTRA_ARGUMENTS = "com.termux.execute.arguments";
|
||||||
|
|
||||||
|
public static final String EXTRA_CURRENT_WORKING_DIRECTORY = "com.termux.execute.cwd";
|
||||||
|
|
||||||
/** This service is only bound from inside the same process and never uses IPC. */
|
/** This service is only bound from inside the same process and never uses IPC. */
|
||||||
class LocalBinder extends Binder {
|
class LocalBinder extends Binder {
|
||||||
@@ -66,7 +70,7 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The terminal sessions which this service manages.
|
* The terminal sessions which this service manages.
|
||||||
*
|
* <p/>
|
||||||
* Note that this list is observed by {@link TermuxActivity#mListViewAdapter}, so any changes must be made on the UI
|
* Note that this list is observed by {@link TermuxActivity#mListViewAdapter}, so any changes must be made on the UI
|
||||||
* thread and followed by a call to {@link ArrayAdapter#notifyDataSetChanged()} }.
|
* thread and followed by a call to {@link ArrayAdapter#notifyDataSetChanged()} }.
|
||||||
*/
|
*/
|
||||||
@@ -113,8 +117,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
} else if (ACTION_EXECUTE.equals(action)) {
|
} else if (ACTION_EXECUTE.equals(action)) {
|
||||||
Uri executableUri = intent.getData();
|
Uri executableUri = intent.getData();
|
||||||
String executablePath = (executableUri == null ? null : executableUri.getPath());
|
String executablePath = (executableUri == null ? null : executableUri.getPath());
|
||||||
String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra("com.termux.execute.arguments"));
|
String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra(EXTRA_ARGUMENTS));
|
||||||
String cwd = intent.getStringExtra("com.termux.execute.cwd");
|
String cwd = intent.getStringExtra(EXTRA_CURRENT_WORKING_DIRECTORY);
|
||||||
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, false);
|
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, false);
|
||||||
|
|
||||||
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
||||||
@@ -237,17 +241,22 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
final String prefixEnv = "PREFIX=" + PREFIX_PATH;
|
final String prefixEnv = "PREFIX=" + PREFIX_PATH;
|
||||||
final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT");
|
final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT");
|
||||||
final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA");
|
final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA");
|
||||||
|
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
||||||
|
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
||||||
|
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE");
|
||||||
String[] env;
|
String[] env;
|
||||||
if (failSafe) {
|
if (failSafe) {
|
||||||
env = new String[] { termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv };
|
// Keep the default path so that system binaries can be used in the failsafe session.
|
||||||
|
final String pathEnv = "PATH=" + System.getenv("PATH");
|
||||||
|
env = new String[]{termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv};
|
||||||
} else {
|
} else {
|
||||||
final String ps1Env = "PS1=$ ";
|
final String ps1Env = "PS1=$ ";
|
||||||
final String ldEnv = "LD_LIBRARY_PATH=" + PREFIX_PATH + "/lib";
|
final String ldEnv = "LD_LIBRARY_PATH=" + PREFIX_PATH + "/lib";
|
||||||
final String langEnv = "LANG=en_US.UTF-8";
|
final String langEnv = "LANG=en_US.UTF-8";
|
||||||
final String pathEnv = "PATH=" + PREFIX_PATH + "/bin:" + PREFIX_PATH + "/bin/applets:" + System.getenv("PATH");
|
final String pathEnv = "PATH=" + PREFIX_PATH + "/bin:" + PREFIX_PATH + "/bin/applets";
|
||||||
final String pwdEnv = "PWD=" + cwd;
|
final String pwdEnv = "PWD=" + cwd;
|
||||||
|
|
||||||
env = new String[] { termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv };
|
env = new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv};
|
||||||
}
|
}
|
||||||
|
|
||||||
String shellName;
|
String shellName;
|
||||||
@@ -268,7 +277,7 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
if (executablePath == null) {
|
if (executablePath == null) {
|
||||||
// Try bash, zsh and ash in that order:
|
// Try bash, zsh and ash in that order:
|
||||||
for (String shellBinary : new String[] { "bash", "zsh", "ash" }) {
|
for (String shellBinary : new String[]{"bash", "zsh", "ash"}) {
|
||||||
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
||||||
if (shellFile.canExecute()) {
|
if (shellFile.canExecute()) {
|
||||||
executablePath = shellFile.getAbsolutePath();
|
executablePath = shellFile.getAbsolutePath();
|
||||||
@@ -291,7 +300,7 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
String[] args;
|
String[] args;
|
||||||
if (arguments == null) {
|
if (arguments == null) {
|
||||||
args = new String[] { shellName };
|
args = new String[]{shellName};
|
||||||
} else {
|
} else {
|
||||||
args = new String[arguments.length + 1];
|
args = new String[arguments.length + 1];
|
||||||
args[0] = shellName;
|
args[0] = shellName;
|
||||||
@@ -325,7 +334,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSessionFinished(final TerminalSession finishedSession) {
|
public void onSessionFinished(final TerminalSession finishedSession) {
|
||||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onSessionFinished(finishedSession);
|
if (mSessionChangeCallback != null)
|
||||||
|
mSessionChangeCallback.onSessionFinished(finishedSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -343,4 +353,9 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onBell(session);
|
if (mSessionChangeCallback != null) mSessionChangeCallback.onBell(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onColorsChanged(TerminalSession session) {
|
||||||
|
if (mSessionChangeCallback != null) mSessionChangeCallback.onColorsChanged(session);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,88 +0,0 @@
|
|||||||
package com.termux.drawer;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.WindowInsets;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides functionality for DrawerLayout unique to API 21
|
|
||||||
*/
|
|
||||||
@SuppressLint("RtlHardcoded")
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
class DrawerLayoutCompatApi21 {
|
|
||||||
|
|
||||||
private static final int[] THEME_ATTRS = { android.R.attr.colorPrimaryDark };
|
|
||||||
|
|
||||||
public static void configureApplyInsets(DrawerLayout drawerLayout) {
|
|
||||||
drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener());
|
|
||||||
drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void dispatchChildInsets(View child, Object insets, int gravity) {
|
|
||||||
WindowInsets wi = (WindowInsets) insets;
|
|
||||||
if (gravity == Gravity.LEFT) {
|
|
||||||
wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom());
|
|
||||||
} else if (gravity == Gravity.RIGHT) {
|
|
||||||
wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom());
|
|
||||||
}
|
|
||||||
child.dispatchApplyWindowInsets(wi);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void applyMarginInsets(ViewGroup.MarginLayoutParams lp, Object insets, int gravity) {
|
|
||||||
WindowInsets wi = (WindowInsets) insets;
|
|
||||||
if (gravity == Gravity.LEFT) {
|
|
||||||
wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom());
|
|
||||||
} else if (gravity == Gravity.RIGHT) {
|
|
||||||
wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom());
|
|
||||||
}
|
|
||||||
lp.leftMargin = wi.getSystemWindowInsetLeft();
|
|
||||||
lp.topMargin = wi.getSystemWindowInsetTop();
|
|
||||||
lp.rightMargin = wi.getSystemWindowInsetRight();
|
|
||||||
lp.bottomMargin = wi.getSystemWindowInsetBottom();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getTopInset(Object insets) {
|
|
||||||
return insets != null ? ((WindowInsets) insets).getSystemWindowInsetTop() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Drawable getDefaultStatusBarBackground(Context context) {
|
|
||||||
final TypedArray a = context.obtainStyledAttributes(THEME_ATTRS);
|
|
||||||
try {
|
|
||||||
return a.getDrawable(0);
|
|
||||||
} finally {
|
|
||||||
a.recycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class InsetsListener implements View.OnApplyWindowInsetsListener {
|
|
||||||
@Override
|
|
||||||
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
|
|
||||||
final DrawerLayout drawerLayout = (DrawerLayout) v;
|
|
||||||
drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0);
|
|
||||||
return insets.consumeSystemWindowInsets();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +0,0 @@
|
|||||||
/**
|
|
||||||
* Extraction (and some minor cleanup to get rid of warnings) of DrawerLayout from the
|
|
||||||
* <a href="http://developer.android.com/tools/support-library/index.html">Android Support Library</a>.
|
|
||||||
*
|
|
||||||
* Source at:
|
|
||||||
* https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v4/java/android/support/v4/widget/DrawerLayout.java
|
|
||||||
* https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v4/java/android/support/v4/widget/ViewDragHelper.java
|
|
||||||
*/
|
|
||||||
package com.termux.drawer;
|
|
||||||
|
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
package com.termux.filepicker;
|
||||||
|
|
||||||
|
import android.content.res.AssetFileDescriptor;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.os.CancellationSignal;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.DocumentsContract.Document;
|
||||||
|
import android.provider.DocumentsContract.Root;
|
||||||
|
import android.provider.DocumentsProvider;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import com.termux.R;
|
||||||
|
import com.termux.app.TermuxService;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A document provider for the Storage Access Framework which exposes the files in the
|
||||||
|
* $HOME/ folder to other apps.
|
||||||
|
* <p/>
|
||||||
|
* Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent:
|
||||||
|
* <p/>
|
||||||
|
* "A document provider and ACTION_GET_CONTENT should be considered mutually exclusive. If you
|
||||||
|
* support both of them simultaneously, your app will appear twice in the system picker UI,
|
||||||
|
* offering two different ways of accessing your stored data. This would be confusing for users."
|
||||||
|
* - http://developer.android.com/guide/topics/providers/document-provider.html#43
|
||||||
|
*/
|
||||||
|
public class TermuxDocumentsProvider extends DocumentsProvider {
|
||||||
|
|
||||||
|
private static final String ALL_MIME_TYPES = "*/*";
|
||||||
|
|
||||||
|
private static final File BASE_DIR = new File(TermuxService.HOME_PATH);
|
||||||
|
|
||||||
|
|
||||||
|
// The default columns to return information about a root if no specific
|
||||||
|
// columns are requested in a query.
|
||||||
|
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
|
||||||
|
Root.COLUMN_ROOT_ID,
|
||||||
|
Root.COLUMN_MIME_TYPES,
|
||||||
|
Root.COLUMN_FLAGS,
|
||||||
|
Root.COLUMN_ICON,
|
||||||
|
Root.COLUMN_TITLE,
|
||||||
|
Root.COLUMN_SUMMARY,
|
||||||
|
Root.COLUMN_DOCUMENT_ID,
|
||||||
|
Root.COLUMN_AVAILABLE_BYTES
|
||||||
|
};
|
||||||
|
|
||||||
|
// The default columns to return information about a document if no specific
|
||||||
|
// columns are requested in a query.
|
||||||
|
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
|
||||||
|
Document.COLUMN_DOCUMENT_ID,
|
||||||
|
Document.COLUMN_MIME_TYPE,
|
||||||
|
Document.COLUMN_DISPLAY_NAME,
|
||||||
|
Document.COLUMN_LAST_MODIFIED,
|
||||||
|
Document.COLUMN_FLAGS,
|
||||||
|
Document.COLUMN_SIZE
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||||
|
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||||
|
@SuppressWarnings("ConstantConditions") final String applicationName = getContext().getString(R.string.application_name);
|
||||||
|
|
||||||
|
final MatrixCursor.RowBuilder row = result.newRow();
|
||||||
|
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
|
||||||
|
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
|
||||||
|
row.add(Root.COLUMN_SUMMARY, null);
|
||||||
|
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH);
|
||||||
|
row.add(Root.COLUMN_TITLE, applicationName);
|
||||||
|
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||||
|
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||||
|
row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||||
|
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||||
|
includeFile(result, documentId, null);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
|
||||||
|
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||||
|
final File parent = getFileForDocId(parentDocumentId);
|
||||||
|
for (File file : parent.listFiles()) {
|
||||||
|
if (!file.getName().startsWith(".")) {
|
||||||
|
includeFile(result, null, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor openDocument(final String documentId, String mode, CancellationSignal signal) throws FileNotFoundException {
|
||||||
|
final File file = getFileForDocId(documentId);
|
||||||
|
final int accessMode = ParcelFileDescriptor.parseMode(mode);
|
||||||
|
return ParcelFileDescriptor.open(file, accessMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
||||||
|
final File file = getFileForDocId(documentId);
|
||||||
|
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||||
|
return new AssetFileDescriptor(pfd, 0, file.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||||
|
File file = getFileForDocId(documentId);
|
||||||
|
if (!file.delete()) {
|
||||||
|
throw new FileNotFoundException("Failed to delete document with id " + documentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||||
|
File file = getFileForDocId(documentId);
|
||||||
|
return getMimeType(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException {
|
||||||
|
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||||
|
final File parent = getFileForDocId(rootId);
|
||||||
|
|
||||||
|
// This example implementation searches file names for the query and doesn't rank search
|
||||||
|
// results, so we can stop as soon as we find a sufficient number of matches. Other
|
||||||
|
// implementations might rank results and use other data about files, rather than the file
|
||||||
|
// name, to produce a match.
|
||||||
|
final LinkedList<File> pending = new LinkedList<>();
|
||||||
|
pending.add(parent);
|
||||||
|
|
||||||
|
final int MAX_SEARCH_RESULTS = 50;
|
||||||
|
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
|
||||||
|
final File file = pending.removeFirst();
|
||||||
|
// Avoid folders outside the $HOME folders linked in to symlinks (to avoid e.g. search
|
||||||
|
// through the whole SD card).
|
||||||
|
boolean isInsideHome;
|
||||||
|
try {
|
||||||
|
isInsideHome = file.getCanonicalPath().startsWith(TermuxService.HOME_PATH);
|
||||||
|
} catch (IOException e) {
|
||||||
|
isInsideHome = true;
|
||||||
|
}
|
||||||
|
final boolean isHidden = file.getName().startsWith(".");
|
||||||
|
if (isInsideHome && !isHidden) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
Collections.addAll(pending, file.listFiles());
|
||||||
|
} else {
|
||||||
|
if (file.getName().toLowerCase().contains(query)) {
|
||||||
|
includeFile(result, null, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the document id given a file. This document id must be consistent across time as other
|
||||||
|
* applications may save the ID and use it to reference documents later.
|
||||||
|
* <p/>
|
||||||
|
* The reverse of @{link #getFileForDocId}.
|
||||||
|
*/
|
||||||
|
private static String getDocIdForFile(File file) {
|
||||||
|
return file.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}).
|
||||||
|
*/
|
||||||
|
private static File getFileForDocId(String docId) throws FileNotFoundException {
|
||||||
|
final File f = new File(docId);
|
||||||
|
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getMimeType(File file) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
return Document.MIME_TYPE_DIR;
|
||||||
|
} else {
|
||||||
|
final String name = file.getName();
|
||||||
|
final int lastDot = name.lastIndexOf('.');
|
||||||
|
if (lastDot >= 0) {
|
||||||
|
final String extension = name.substring(lastDot + 1);
|
||||||
|
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||||
|
if (mime != null) return mime;
|
||||||
|
}
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a representation of a file to a cursor.
|
||||||
|
*
|
||||||
|
* @param result the cursor to modify
|
||||||
|
* @param docId the document ID representing the desired file (may be null if given file)
|
||||||
|
* @param file the File object representing the desired file (may be null if given docID)
|
||||||
|
*/
|
||||||
|
private void includeFile(MatrixCursor result, String docId, File file)
|
||||||
|
throws FileNotFoundException {
|
||||||
|
if (docId == null) {
|
||||||
|
docId = getDocIdForFile(file);
|
||||||
|
} else {
|
||||||
|
file = getFileForDocId(docId);
|
||||||
|
}
|
||||||
|
|
||||||
|
int flags = 0;
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
if (file.isDirectory() && file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||||
|
} else if (file.canWrite()) {
|
||||||
|
flags |= Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_DELETE;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String displayName = file.getName();
|
||||||
|
final String mimeType = getMimeType(file);
|
||||||
|
if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||||
|
|
||||||
|
final MatrixCursor.RowBuilder row = result.newRow();
|
||||||
|
row.add(Document.COLUMN_DOCUMENT_ID, docId);
|
||||||
|
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||||
|
row.add(Document.COLUMN_SIZE, file.length());
|
||||||
|
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||||
|
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||||
|
row.add(Document.COLUMN_FLAGS, flags);
|
||||||
|
row.add(Document.COLUMN_ICON, R.drawable.ic_launcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
package com.termux.filepicker;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.OpenableColumns;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Patterns;
|
||||||
|
|
||||||
|
import com.termux.R;
|
||||||
|
import com.termux.app.DialogUtils;
|
||||||
|
import com.termux.app.TermuxService;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class TermuxFileReceiverActivity extends Activity {
|
||||||
|
|
||||||
|
static final String TERMUX_RECEIVEDIR = TermuxService.FILES_PATH + "/home/downloads";
|
||||||
|
static final String EDITOR_PROGRAM = TermuxService.HOME_PATH + "/bin/termux-file-editor";
|
||||||
|
static final String URL_OPENER_PROGRAM = TermuxService.HOME_PATH + "/bin/termux-url-opener";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the activity should be finished when the name input dialog is dismissed. This is disabled
|
||||||
|
* before showing an error dialog, since the act of showing the error dialog will cause the
|
||||||
|
* name input dialog to be implicitly dismissed, and we do not want to finish the activity directly
|
||||||
|
* when showing the error dialog.
|
||||||
|
*/
|
||||||
|
boolean mFinishOnDismissNameDialog = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
final Intent intent = getIntent();
|
||||||
|
final String action = intent.getAction();
|
||||||
|
final String type = intent.getType();
|
||||||
|
final String scheme = intent.getScheme();
|
||||||
|
|
||||||
|
if (Intent.ACTION_SEND.equals(action) && type != null) {
|
||||||
|
final String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||||
|
final Uri sharedUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||||
|
|
||||||
|
if (sharedText != null) {
|
||||||
|
if (Patterns.WEB_URL.matcher(sharedText).matches()) {
|
||||||
|
handleUrlAndFinish(sharedText);
|
||||||
|
} else {
|
||||||
|
String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
|
||||||
|
if (subject == null) subject = intent.getStringExtra(Intent.EXTRA_TITLE);
|
||||||
|
if (subject != null) subject += ".txt";
|
||||||
|
promptNameAndSave(new ByteArrayInputStream(sharedText.getBytes(StandardCharsets.UTF_8)), subject);
|
||||||
|
}
|
||||||
|
} else if (sharedUri != null) {
|
||||||
|
handleContentUri(sharedUri, intent.getStringExtra(Intent.EXTRA_TITLE));
|
||||||
|
} else {
|
||||||
|
showErrorDialogAndQuit("Send action without content - nothing to save.");
|
||||||
|
}
|
||||||
|
} else if ("content".equals(scheme)) {
|
||||||
|
handleContentUri(intent.getData(), intent.getStringExtra(Intent.EXTRA_TITLE));
|
||||||
|
} else if ("file".equals(scheme)) {
|
||||||
|
// When e.g. clicking on a downloaded apk:
|
||||||
|
String path = intent.getData().getPath();
|
||||||
|
File file = new File(path);
|
||||||
|
try {
|
||||||
|
FileInputStream in = new FileInputStream(file);
|
||||||
|
promptNameAndSave(in, file.getName());
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
showErrorDialogAndQuit("Cannot open file: " + e.getMessage() + ".");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showErrorDialogAndQuit("Unable to receive any file or URL.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void showErrorDialogAndQuit(String message) {
|
||||||
|
mFinishOnDismissNameDialog = false;
|
||||||
|
new AlertDialog.Builder(this).setMessage(message).setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss(DialogInterface dialog) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}).setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleContentUri(final Uri uri, String subjectFromIntent) {
|
||||||
|
try {
|
||||||
|
String attachmentFileName = null;
|
||||||
|
|
||||||
|
String[] projection = new String[]{OpenableColumns.DISPLAY_NAME};
|
||||||
|
try (Cursor c = getContentResolver().query(uri, projection, null, null, null)) {
|
||||||
|
if (c != null && c.moveToFirst()) {
|
||||||
|
final int fileNameColumnId = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||||
|
if (fileNameColumnId >= 0) attachmentFileName = c.getString(fileNameColumnId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachmentFileName == null) attachmentFileName = subjectFromIntent;
|
||||||
|
|
||||||
|
InputStream in = getContentResolver().openInputStream(uri);
|
||||||
|
promptNameAndSave(in, attachmentFileName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
showErrorDialogAndQuit("Unable to handle shared content:\n\n" + e.getMessage());
|
||||||
|
Log.e("termux", "handleContentUri(uri=" + uri + ") failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void promptNameAndSave(final InputStream in, final String attachmentFileName) {
|
||||||
|
DialogUtils.textInput(this, R.string.file_received_title, attachmentFileName, R.string.file_received_edit_button, new DialogUtils.TextSetListener() {
|
||||||
|
@Override
|
||||||
|
public void onTextSet(String text) {
|
||||||
|
File outFile = saveStreamWithName(in, text);
|
||||||
|
if (outFile == null) return;
|
||||||
|
|
||||||
|
final File editorProgramFile = new File(EDITOR_PROGRAM);
|
||||||
|
if (!editorProgramFile.isFile()) {
|
||||||
|
showErrorDialogAndQuit("The following file does not exist:\n$HOME/bin/termux-file-editor\n\n"
|
||||||
|
+ "Create this file as a script or a symlink - it will be called with the received file as only argument.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do this for the user if necessary:
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
editorProgramFile.setExecutable(true);
|
||||||
|
|
||||||
|
final Uri scriptUri = new Uri.Builder().scheme("file").path(EDITOR_PROGRAM).build();
|
||||||
|
|
||||||
|
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE, scriptUri);
|
||||||
|
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
|
||||||
|
executeIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, new String[]{outFile.getAbsolutePath()});
|
||||||
|
startService(executeIntent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
R.string.file_received_open_folder_button, new DialogUtils.TextSetListener() {
|
||||||
|
@Override
|
||||||
|
public void onTextSet(String text) {
|
||||||
|
if (saveStreamWithName(in, text) == null) return;
|
||||||
|
|
||||||
|
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE);
|
||||||
|
executeIntent.putExtra(TermuxService.EXTRA_CURRENT_WORKING_DIRECTORY, TERMUX_RECEIVEDIR);
|
||||||
|
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
|
||||||
|
startService(executeIntent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
android.R.string.cancel, new DialogUtils.TextSetListener() {
|
||||||
|
@Override
|
||||||
|
public void onTextSet(final String text) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}, new DialogInterface.OnDismissListener() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss(DialogInterface dialog) {
|
||||||
|
if (mFinishOnDismissNameDialog) finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public File saveStreamWithName(InputStream in, String attachmentFileName) {
|
||||||
|
File receiveDir = new File(TERMUX_RECEIVEDIR);
|
||||||
|
if (!receiveDir.isDirectory() && !receiveDir.mkdirs()) {
|
||||||
|
showErrorDialogAndQuit("Cannot create directory: " + receiveDir.getAbsolutePath());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final File outFile = new File(receiveDir, attachmentFileName);
|
||||||
|
try (FileOutputStream f = new FileOutputStream(outFile)) {
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int readBytes;
|
||||||
|
while ((readBytes = in.read(buffer)) > 0) {
|
||||||
|
f.write(buffer, 0, readBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outFile;
|
||||||
|
} catch (IOException e) {
|
||||||
|
showErrorDialogAndQuit("Error saving file:\n\n" + e);
|
||||||
|
Log.e("termux", "Error saving file", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleUrlAndFinish(final String url) {
|
||||||
|
final File urlOpenerProgramFile = new File(URL_OPENER_PROGRAM);
|
||||||
|
if (!urlOpenerProgramFile.isFile()) {
|
||||||
|
showErrorDialogAndQuit("The following file does not exist:\n$HOME/bin/termux-url-opener\n\n"
|
||||||
|
+ "Create this file as a script or a symlink - it will be called with the shared URL as only argument.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do this for the user if necessary:
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
urlOpenerProgramFile.setExecutable(true);
|
||||||
|
|
||||||
|
final Uri urlOpenerProgramUri = new Uri.Builder().scheme("file").path(URL_OPENER_PROGRAM).build();
|
||||||
|
|
||||||
|
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE, urlOpenerProgramUri);
|
||||||
|
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
|
||||||
|
executeIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, new String[]{url});
|
||||||
|
startService(executeIntent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ final class ByteQueue {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to write the specified portion of the provided buffer to the queue.
|
* 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.
|
* Returns whether the output was totally written, false if it was closed before.
|
||||||
*/
|
*/
|
||||||
public boolean write(byte[] buffer, int offset, int lengthToWrite) {
|
public boolean write(byte[] buffer, int offset, int lengthToWrite) {
|
||||||
|
|||||||
@@ -12,23 +12,18 @@ final class JNI {
|
|||||||
/**
|
/**
|
||||||
* Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the
|
* Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the
|
||||||
* subprocess.
|
* subprocess.
|
||||||
*
|
* <p/>
|
||||||
* Callers are responsible for calling {@link #close(int)} on the returned file descriptor.
|
* Callers are responsible for calling {@link #close(int)} on the returned file descriptor.
|
||||||
*
|
*
|
||||||
* @param cmd
|
* @param cmd The command to execute
|
||||||
* The command to execute
|
* @param cwd The current working directory for the executed command
|
||||||
* @param cwd
|
* @param args An array of arguments to the command
|
||||||
* The current working directory for the executed command
|
* @param envVars An array of strings of the form "VAR=value" to be added to the environment of the process
|
||||||
* @param args
|
* @param processId A one-element array to which the process ID of the started process will be written.
|
||||||
* 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
|
* @return the file descriptor resulting from opening /dev/ptmx master device. The sub process will have opened the
|
||||||
* slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr.
|
* slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr.
|
||||||
*/
|
*/
|
||||||
public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId);
|
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. */
|
/** 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);
|
public static native void setPtyWindowSize(int fd, int rows, int cols);
|
||||||
@@ -40,15 +35,6 @@ final class JNI {
|
|||||||
*/
|
*/
|
||||||
public static native int waitFor(int processId);
|
public static native int waitFor(int processId);
|
||||||
|
|
||||||
/**
|
|
||||||
* Send SIGHUP to a process group.
|
|
||||||
*
|
|
||||||
* There exists a kill(2) system call wrapper in {@link android.os.Process#sendSignal(int, int)}, but that makes a
|
|
||||||
* "if (pid > 0)" check so cannot be used for sending to a process group:
|
|
||||||
* https://android.googlesource.com/platform/frameworks/base/+/donut-release/core/jni/android_util_Process.cpp
|
|
||||||
*/
|
|
||||||
public static native void hangupProcessGroup(int processId);
|
|
||||||
|
|
||||||
/** Close a file descriptor through the close(2) system call. */
|
/** Close a file descriptor through the close(2) system call. */
|
||||||
public static native void close(int fileDescriptor);
|
public static native void close(int fileDescriptor);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
|
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_BREAK;
|
||||||
import static android.view.KeyEvent.KEYCODE_DEL;
|
import static android.view.KeyEvent.KEYCODE_DEL;
|
||||||
import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
|
import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
|
||||||
@@ -24,6 +28,7 @@ import static android.view.KeyEvent.KEYCODE_F9;
|
|||||||
import static android.view.KeyEvent.KEYCODE_FORWARD_DEL;
|
import static android.view.KeyEvent.KEYCODE_FORWARD_DEL;
|
||||||
import static android.view.KeyEvent.KEYCODE_INSERT;
|
import static android.view.KeyEvent.KEYCODE_INSERT;
|
||||||
import static android.view.KeyEvent.KEYCODE_MOVE_END;
|
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_0;
|
||||||
import static android.view.KeyEvent.KEYCODE_NUMPAD_1;
|
import static android.view.KeyEvent.KEYCODE_NUMPAD_1;
|
||||||
import static android.view.KeyEvent.KEYCODE_NUMPAD_2;
|
import static android.view.KeyEvent.KEYCODE_NUMPAD_2;
|
||||||
@@ -45,14 +50,9 @@ import static android.view.KeyEvent.KEYCODE_NUMPAD_SUBTRACT;
|
|||||||
import static android.view.KeyEvent.KEYCODE_NUM_LOCK;
|
import static android.view.KeyEvent.KEYCODE_NUM_LOCK;
|
||||||
import static android.view.KeyEvent.KEYCODE_PAGE_DOWN;
|
import static android.view.KeyEvent.KEYCODE_PAGE_DOWN;
|
||||||
import static android.view.KeyEvent.KEYCODE_PAGE_UP;
|
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_SYSRQ;
|
||||||
import static android.view.KeyEvent.KEYCODE_TAB;
|
import static android.view.KeyEvent.KEYCODE_TAB;
|
||||||
import static android.view.KeyEvent.KEYCODE_HOME;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
|
|
||||||
public final class KeyHandler {
|
public final class KeyHandler {
|
||||||
|
|
||||||
@@ -61,11 +61,12 @@ public final class KeyHandler {
|
|||||||
public static final int KEYMOD_SHIFT = 0x20000000;
|
public static final int KEYMOD_SHIFT = 0x20000000;
|
||||||
|
|
||||||
private static final Map<String, Integer> TERMCAP_TO_KEYCODE = new HashMap<>();
|
private static final Map<String, Integer> TERMCAP_TO_KEYCODE = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html
|
// terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html
|
||||||
// termcap: http://man7.org/linux/man-pages/man5/termcap.5.html
|
// termcap: http://man7.org/linux/man-pages/man5/termcap.5.html
|
||||||
TERMCAP_TO_KEYCODE.put("%i", KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT);
|
TERMCAP_TO_KEYCODE.put("%i", KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT);
|
||||||
TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_HOME); // Shifted home
|
TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_MOVE_HOME); // Shifted home
|
||||||
TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT);
|
TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT);
|
||||||
TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key
|
TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ public final class KeyHandler {
|
|||||||
TERMCAP_TO_KEYCODE.put("kb", KEYCODE_DEL); // backspace key
|
TERMCAP_TO_KEYCODE.put("kb", KEYCODE_DEL); // backspace key
|
||||||
|
|
||||||
TERMCAP_TO_KEYCODE.put("kd", KEYCODE_DPAD_DOWN); // terminfo=kcud1, down-arrow key
|
TERMCAP_TO_KEYCODE.put("kd", KEYCODE_DPAD_DOWN); // terminfo=kcud1, down-arrow key
|
||||||
TERMCAP_TO_KEYCODE.put("kh", KeyEvent.KEYCODE_HOME);
|
TERMCAP_TO_KEYCODE.put("kh", KEYCODE_MOVE_HOME);
|
||||||
TERMCAP_TO_KEYCODE.put("kl", KEYCODE_DPAD_LEFT);
|
TERMCAP_TO_KEYCODE.put("kl", KEYCODE_DPAD_LEFT);
|
||||||
TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT);
|
TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT);
|
||||||
|
|
||||||
@@ -106,10 +107,10 @@ public final class KeyHandler {
|
|||||||
// t_K3 <kPageUp> keypad page-up key
|
// t_K3 <kPageUp> keypad page-up key
|
||||||
// t_K4 <kEnd> keypad end key
|
// t_K4 <kEnd> keypad end key
|
||||||
// t_K5 <kPageDown> keypad page-down key
|
// t_K5 <kPageDown> keypad page-down key
|
||||||
TERMCAP_TO_KEYCODE.put("K1", KeyEvent.KEYCODE_HOME);
|
TERMCAP_TO_KEYCODE.put("K1", KEYCODE_MOVE_HOME);
|
||||||
TERMCAP_TO_KEYCODE.put("K3", KeyEvent.KEYCODE_PAGE_UP);
|
TERMCAP_TO_KEYCODE.put("K3", KEYCODE_PAGE_UP);
|
||||||
TERMCAP_TO_KEYCODE.put("K4", KeyEvent.KEYCODE_MOVE_END);
|
TERMCAP_TO_KEYCODE.put("K4", KEYCODE_MOVE_END);
|
||||||
TERMCAP_TO_KEYCODE.put("K5", KeyEvent.KEYCODE_PAGE_DOWN);
|
TERMCAP_TO_KEYCODE.put("K5", KEYCODE_PAGE_DOWN);
|
||||||
|
|
||||||
TERMCAP_TO_KEYCODE.put("ku", KEYCODE_DPAD_UP);
|
TERMCAP_TO_KEYCODE.put("ku", KEYCODE_DPAD_UP);
|
||||||
|
|
||||||
@@ -161,7 +162,9 @@ public final class KeyHandler {
|
|||||||
case KEYCODE_DPAD_LEFT:
|
case KEYCODE_DPAD_LEFT:
|
||||||
return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D');
|
return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D');
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_HOME:
|
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');
|
return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H');
|
||||||
case KEYCODE_MOVE_END:
|
case KEYCODE_MOVE_END:
|
||||||
return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F');
|
return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F');
|
||||||
@@ -208,7 +211,7 @@ public final class KeyHandler {
|
|||||||
return "\033[34~"; // Pause/Break
|
return "\033[34~"; // Pause/Break
|
||||||
|
|
||||||
case KEYCODE_ESCAPE:
|
case KEYCODE_ESCAPE:
|
||||||
case KeyEvent.KEYCODE_BACK:
|
case KEYCODE_BACK:
|
||||||
return "\033";
|
return "\033";
|
||||||
|
|
||||||
case KEYCODE_INSERT:
|
case KEYCODE_INSERT:
|
||||||
@@ -216,20 +219,18 @@ public final class KeyHandler {
|
|||||||
case KEYCODE_FORWARD_DEL:
|
case KEYCODE_FORWARD_DEL:
|
||||||
return transformForModifiers("\033[3", keyMode, '~');
|
return transformForModifiers("\033[3", keyMode, '~');
|
||||||
|
|
||||||
case KEYCODE_NUMPAD_DOT:
|
|
||||||
return keypadApplication ? "\033On" : "\033[3~";
|
|
||||||
|
|
||||||
case KEYCODE_PAGE_UP:
|
case KEYCODE_PAGE_UP:
|
||||||
return "\033[5~";
|
return "\033[5~";
|
||||||
case KEYCODE_PAGE_DOWN:
|
case KEYCODE_PAGE_DOWN:
|
||||||
return "\033[6~";
|
return "\033[6~";
|
||||||
case KEYCODE_DEL:
|
case KEYCODE_DEL:
|
||||||
// Yes, this needs to U+007F and not U+0008!
|
String prefix = ((keyMode & KEYMOD_ALT) == 0) ? "" : "\033";
|
||||||
return "\u007F";
|
// Just do what xterm and gnome-terminal does:
|
||||||
|
return prefix + (((keyMode & KEYMOD_CTRL) == 0) ? "\u007F" : "\u0008");
|
||||||
case KEYCODE_NUM_LOCK:
|
case KEYCODE_NUM_LOCK:
|
||||||
return "\033OP";
|
return "\033OP";
|
||||||
|
|
||||||
case KeyEvent.KEYCODE_SPACE:
|
case KEYCODE_SPACE:
|
||||||
// If ctrl is not down, return null so that it goes through normal input processing (which may e.g. cause a
|
// 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):
|
// combining accent to be written):
|
||||||
return ((keyMode & KEYMOD_CTRL) == 0) ? null : "\0";
|
return ((keyMode & KEYMOD_CTRL) == 0) ? null : "\0";
|
||||||
@@ -247,12 +248,14 @@ public final class KeyHandler {
|
|||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+";
|
||||||
case KEYCODE_NUMPAD_COMMA:
|
case KEYCODE_NUMPAD_COMMA:
|
||||||
return ",";
|
return ",";
|
||||||
|
case KEYCODE_NUMPAD_DOT:
|
||||||
|
return keypadApplication ? "\033On" : ".";
|
||||||
case KEYCODE_NUMPAD_SUBTRACT:
|
case KEYCODE_NUMPAD_SUBTRACT:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
|
||||||
case KEYCODE_NUMPAD_DIVIDE:
|
case KEYCODE_NUMPAD_DIVIDE:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
|
||||||
case KEYCODE_NUMPAD_0:
|
case KEYCODE_NUMPAD_0:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "1";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "0";
|
||||||
case KEYCODE_NUMPAD_1:
|
case KEYCODE_NUMPAD_1:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
|
||||||
case KEYCODE_NUMPAD_2:
|
case KEYCODE_NUMPAD_2:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.termux.terminal;
|
|||||||
/**
|
/**
|
||||||
* A circular buffer of {@link TerminalRow}:s which keeps notes about what is visible on a logical screen and the scroll
|
* A circular buffer of {@link TerminalRow}:s which keeps notes about what is visible on a logical screen and the scroll
|
||||||
* history.
|
* history.
|
||||||
*
|
* <p/>
|
||||||
* See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices.
|
* See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices.
|
||||||
*/
|
*/
|
||||||
public final class TerminalBuffer {
|
public final class TerminalBuffer {
|
||||||
@@ -21,12 +21,9 @@ public final class TerminalBuffer {
|
|||||||
/**
|
/**
|
||||||
* Create a transcript screen.
|
* Create a transcript screen.
|
||||||
*
|
*
|
||||||
* @param columns
|
* @param columns the width of the screen in characters.
|
||||||
* the width of the screen in characters.
|
* @param totalRows the height of the entire text area, in rows of text.
|
||||||
* @param totalRows
|
* @param screenRows the height of just the screen, not including the transcript that holds lines that have scrolled off
|
||||||
* 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.
|
* the top of the screen.
|
||||||
*/
|
*/
|
||||||
public TerminalBuffer(int columns, int totalRows, int screenRows) {
|
public TerminalBuffer(int columns, int totalRows, int screenRows) {
|
||||||
@@ -61,6 +58,10 @@ public final class TerminalBuffer {
|
|||||||
TerminalRow lineObject = mLines[externalToInternalRow(row)];
|
TerminalRow lineObject = mLines[externalToInternalRow(row)];
|
||||||
int x1Index = lineObject.findStartOfColumn(x1);
|
int x1Index = lineObject.findStartOfColumn(x1);
|
||||||
int x2Index = (x2 < mColumns) ? lineObject.findStartOfColumn(x2) : lineObject.getSpaceUsed();
|
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;
|
char[] line = lineObject.mText;
|
||||||
int lastPrintingCharIndex = -1;
|
int lastPrintingCharIndex = -1;
|
||||||
int i;
|
int i;
|
||||||
@@ -71,10 +72,11 @@ public final class TerminalBuffer {
|
|||||||
} else {
|
} else {
|
||||||
for (i = x1Index; i < x2Index; ++i) {
|
for (i = x1Index; i < x2Index; ++i) {
|
||||||
char c = line[i];
|
char c = line[i];
|
||||||
if (c != ' ' && !Character.isLowSurrogate(c)) lastPrintingCharIndex = i;
|
if (c != ' ') lastPrintingCharIndex = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (lastPrintingCharIndex != -1) builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
|
if (lastPrintingCharIndex != -1)
|
||||||
|
builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
|
||||||
if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n');
|
if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n');
|
||||||
}
|
}
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
@@ -90,15 +92,15 @@ public final class TerminalBuffer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a row value from the public external coordinate system to our internal private coordinate system.
|
* Convert a row value from the public external coordinate system to our internal private coordinate system.
|
||||||
*
|
* <p/>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
|
* <li>External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
|
||||||
* <li>Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
|
* <li>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).
|
* mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
* <p/>
|
||||||
* External <---> Internal:
|
* External <---> Internal:
|
||||||
*
|
* <p/>
|
||||||
* <pre>
|
* <pre>
|
||||||
* [ ... ] [ ... ]
|
* [ ... ] [ ... ]
|
||||||
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
|
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
|
||||||
@@ -108,8 +110,7 @@ public final class TerminalBuffer {
|
|||||||
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
|
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param externalRow
|
* @param externalRow a row in the external coordinate system.
|
||||||
* a row in the external coordinate system.
|
|
||||||
* @return The row corresponding to the input argument in the private coordinate system.
|
* @return The row corresponding to the input argument in the private coordinate system.
|
||||||
*/
|
*/
|
||||||
public int externalToInternalRow(int externalRow) {
|
public int externalToInternalRow(int externalRow) {
|
||||||
@@ -123,22 +124,23 @@ public final class TerminalBuffer {
|
|||||||
mLines[externalToInternalRow(row)].mLineWrap = true;
|
mLines[externalToInternalRow(row)].mLineWrap = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean getLineWrap(int row) {
|
public boolean getLineWrap(int row) {
|
||||||
return mLines[externalToInternalRow(row)].mLineWrap;
|
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
|
* 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).
|
* change or the rows expand (that is, it only works when shrinking the number of rows).
|
||||||
*
|
*
|
||||||
* @param newColumns
|
* @param newColumns The number of columns the screen should have.
|
||||||
* The number of columns the screen should have.
|
* @param newRows The number of rows the screen should have.
|
||||||
* @param newRows
|
* @param cursor An int[2] containing the (column, row) cursor location.
|
||||||
* The number of rows the screen should have.
|
|
||||||
* @param cursor
|
|
||||||
* An int[2] containing the (column, row) cursor location.
|
|
||||||
*/
|
*/
|
||||||
public void resize(int newColumns, int newRows, int newTotalRows, int[] cursor, int currentStyle, boolean altScreen) {
|
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):
|
// newRows > mTotalRows should not normally happen since mTotalRows is TRANSCRIPT_ROWS (10000):
|
||||||
if (newColumns == mColumns && newRows <= mTotalRows) {
|
if (newColumns == mColumns && newRows <= mTotalRows) {
|
||||||
// Fast resize where just the rows changed.
|
// Fast resize where just the rows changed.
|
||||||
@@ -230,11 +232,12 @@ public final class TerminalBuffer {
|
|||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < oldLine.getSpaceUsed(); i++)
|
for (int i = 0; i < oldLine.getSpaceUsed(); i++)
|
||||||
// NEWLY INTRODUCED BUG! Should not index oldLine.mStyle with char indices
|
// NEWLY INTRODUCED BUG! Should not index oldLine.mStyle with char indices
|
||||||
if (oldLine.mText[i] != ' '/* || oldLine.mStyle[i] != currentStyle */) lastNonSpaceIndex = i + 1;
|
if (oldLine.mText[i] != ' '/* || oldLine.mStyle[i] != currentStyle */)
|
||||||
|
lastNonSpaceIndex = i + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int currentOldCol = 0;
|
int currentOldCol = 0;
|
||||||
int styleAtCol = 0;
|
long styleAtCol = 0;
|
||||||
for (int i = 0; i < lastNonSpaceIndex; i++) {
|
for (int i = 0; i < lastNonSpaceIndex; i++) {
|
||||||
// Note that looping over java character, not cells.
|
// Note that looping over java character, not cells.
|
||||||
char c = oldLine.mText[i];
|
char c = oldLine.mText[i];
|
||||||
@@ -294,10 +297,8 @@ public final class TerminalBuffer {
|
|||||||
* Block copy lines and associated metadata from one location to another in the circular buffer, taking wraparound
|
* Block copy lines and associated metadata from one location to another in the circular buffer, taking wraparound
|
||||||
* into account.
|
* into account.
|
||||||
*
|
*
|
||||||
* @param srcInternal
|
* @param srcInternal The first line to be copied.
|
||||||
* The first line to be copied.
|
* @param len The number of lines to be copied.
|
||||||
* @param len
|
|
||||||
* The number of lines to be copied.
|
|
||||||
*/
|
*/
|
||||||
private void blockCopyLinesDown(int srcInternal, int len) {
|
private void blockCopyLinesDown(int srcInternal, int len) {
|
||||||
if (len == 0) return;
|
if (len == 0) return;
|
||||||
@@ -316,14 +317,11 @@ public final class TerminalBuffer {
|
|||||||
/**
|
/**
|
||||||
* Scroll the screen down one line. To scroll the whole screen of a 24 line screen, the arguments would be (0, 24).
|
* Scroll the screen down one line. To scroll the whole screen of a 24 line screen, the arguments would be (0, 24).
|
||||||
*
|
*
|
||||||
* @param topMargin
|
* @param topMargin First line that is scrolled.
|
||||||
* First line that is scrolled.
|
* @param bottomMargin One line after the last line that is scrolled.
|
||||||
* @param bottomMargin
|
* @param style the style for the newly exposed line.
|
||||||
* 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, int style) {
|
public void scrollDownOneLine(int topMargin, int bottomMargin, long style) {
|
||||||
if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows)
|
if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows)
|
||||||
throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + mScreenRows);
|
throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + mScreenRows);
|
||||||
|
|
||||||
@@ -352,18 +350,12 @@ public final class TerminalBuffer {
|
|||||||
* of the source and destination must be within the bounds of the screen, or else an InvalidParameterException will
|
* of the source and destination must be within the bounds of the screen, or else an InvalidParameterException will
|
||||||
* be thrown.
|
* be thrown.
|
||||||
*
|
*
|
||||||
* @param sx
|
* @param sx source X coordinate
|
||||||
* source X coordinate
|
* @param sy source Y coordinate
|
||||||
* @param sy
|
* @param w width
|
||||||
* source Y coordinate
|
* @param h height
|
||||||
* @param w
|
* @param dx destination X coordinate
|
||||||
* width
|
* @param dy destination Y coordinate
|
||||||
* @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) {
|
public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
|
||||||
if (w == 0) return;
|
if (w == 0) return;
|
||||||
@@ -382,7 +374,7 @@ public final class TerminalBuffer {
|
|||||||
* InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block
|
* InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block
|
||||||
* of characters.
|
* of characters.
|
||||||
*/
|
*/
|
||||||
public void blockSet(int sx, int sy, int w, int h, int val, int style) {
|
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) {
|
if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")");
|
"Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")");
|
||||||
@@ -396,14 +388,14 @@ public final class TerminalBuffer {
|
|||||||
return (mLines[row] == null) ? (mLines[row] = new TerminalRow(mColumns, 0)) : mLines[row];
|
return (mLines[row] == null) ? (mLines[row] = new TerminalRow(mColumns, 0)) : mLines[row];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChar(int column, int row, int codePoint, int style) {
|
public void setChar(int column, int row, int codePoint, long style) {
|
||||||
if (row >= mScreenRows || column >= mColumns)
|
if (row >= mScreenRows || column >= mColumns)
|
||||||
throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
|
throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
|
||||||
row = externalToInternalRow(row);
|
row = externalToInternalRow(row);
|
||||||
allocateFullLineIfNecessary(row).setChar(column, codePoint, style);
|
allocateFullLineIfNecessary(row).setChar(column, codePoint, style);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getStyleAt(int externalRow, int column) {
|
public long getStyleAt(int externalRow, int column) {
|
||||||
return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column);
|
return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +407,7 @@ public final class TerminalBuffer {
|
|||||||
int startOfLine = (rectangular || y == top) ? left : leftMargin;
|
int startOfLine = (rectangular || y == top) ? left : leftMargin;
|
||||||
int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin;
|
int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin;
|
||||||
for (int x = startOfLine; x < endOfLine; x++) {
|
for (int x = startOfLine; x < endOfLine; x++) {
|
||||||
int currentStyle = line.getStyle(x);
|
long currentStyle = line.getStyle(x);
|
||||||
int foreColor = TextStyle.decodeForeColor(currentStyle);
|
int foreColor = TextStyle.decodeForeColor(currentStyle);
|
||||||
int backColor = TextStyle.decodeBackColor(currentStyle);
|
int backColor = TextStyle.decodeBackColor(currentStyle);
|
||||||
int effect = TextStyle.decodeEffect(currentStyle);
|
int effect = TextStyle.decodeEffect(currentStyle);
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public final class TerminalColorScheme {
|
|||||||
0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee,
|
0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee,
|
||||||
|
|
||||||
// COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR:
|
// COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR:
|
||||||
0xffffffff, 0xff000000, 0xffffffff };
|
0xffffffff, 0xff000000, 0xffffffff};
|
||||||
|
|
||||||
public final int[] mDefaultColors = new int[TextStyle.NUM_INDEXED_COLORS];
|
public final int[] mDefaultColors = new int[TextStyle.NUM_INDEXED_COLORS];
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ public final class TerminalColorScheme {
|
|||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
private void reset() {
|
||||||
System.arraycopy(DEFAULT_COLORSCHEME, 0, mDefaultColors, 0, TextStyle.NUM_INDEXED_COLORS);
|
System.arraycopy(DEFAULT_COLORSCHEME, 0, mDefaultColors, 0, TextStyle.NUM_INDEXED_COLORS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,8 @@ public final class TerminalColorScheme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int colorValue = TerminalColors.parse(value);
|
int colorValue = TerminalColors.parse(value);
|
||||||
if (colorValue == 0) throw new IllegalArgumentException("Property '" + key + "' has invalid color: '" + value + "'");
|
if (colorValue == 0)
|
||||||
|
throw new IllegalArgumentException("Property '" + key + "' has invalid color: '" + value + "'");
|
||||||
|
|
||||||
mDefaultColors[colorIndex] = colorValue;
|
mDefaultColors[colorIndex] = colorValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public final class TerminalColors {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse color according to http://manpages.ubuntu.com/manpages/intrepid/man3/XQueryColor.3.html
|
* 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.
|
* Highest bit is set if successful, so return value is 0xFF${R}${G}${B}. Return 0 if failed.
|
||||||
*/
|
*/
|
||||||
static int parse(String c) {
|
static int parse(String c) {
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
import android.util.Base64;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders text into a screen. Contains all the terminal-specific knowledge and state. Emulates a subset of the X Window
|
* Renders text into a screen. Contains all the terminal-specific knowledge and state. Emulates a subset of the X Window
|
||||||
* System xterm terminal, which in turn is an emulator for a subset of the Digital Equipment Corporation vt100 terminal.
|
* System xterm terminal, which in turn is an emulator for a subset of the Digital Equipment Corporation vt100 terminal.
|
||||||
*
|
* <p>
|
||||||
* References:
|
* References:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>http://invisible-island.net/xterm/ctlseqs/ctlseqs.html</li>
|
* <li>http://invisible-island.net/xterm/ctlseqs/ctlseqs.html</li>
|
||||||
@@ -33,8 +33,7 @@ public final class TerminalEmulator {
|
|||||||
private static final boolean LOG_ESCAPE_SEQUENCES = false;
|
private static final boolean LOG_ESCAPE_SEQUENCES = false;
|
||||||
|
|
||||||
public static final int MOUSE_LEFT_BUTTON = 0;
|
public static final int MOUSE_LEFT_BUTTON = 0;
|
||||||
public static final int MOUSE_MIDDLE_BUTTON = 1;
|
|
||||||
public static final int MOUSE_RIGHT_BUTTON = 2;
|
|
||||||
/** Mouse moving while having left mouse button pressed. */
|
/** Mouse moving while having left mouse button pressed. */
|
||||||
public static final int MOUSE_LEFT_BUTTON_MOVED = 32;
|
public static final int MOUSE_LEFT_BUTTON_MOVED = 32;
|
||||||
public static final int MOUSE_WHEELUP_BUTTON = 64;
|
public static final int MOUSE_WHEELUP_BUTTON = 64;
|
||||||
@@ -146,7 +145,7 @@ public final class TerminalEmulator {
|
|||||||
/**
|
/**
|
||||||
* The alternate screen buffer, exactly as large as the display and contains no additional saved lines (so that when
|
* The alternate screen buffer, exactly as large as the display and contains no additional saved lines (so that when
|
||||||
* the alternate screen buffer is active, you cannot scroll back to view saved lines).
|
* the alternate screen buffer is active, you cannot scroll back to view saved lines).
|
||||||
*
|
* <p>
|
||||||
* See http://www.xfree86.org/current/ctlseqs.html#The%20Alternate%20Screen%20Buffer
|
* See http://www.xfree86.org/current/ctlseqs.html#The%20Alternate%20Screen%20Buffer
|
||||||
*/
|
*/
|
||||||
final TerminalBuffer mAltBuffer;
|
final TerminalBuffer mAltBuffer;
|
||||||
@@ -207,10 +206,15 @@ public final class TerminalEmulator {
|
|||||||
*/
|
*/
|
||||||
private boolean mAboutToAutoWrap;
|
private boolean mAboutToAutoWrap;
|
||||||
|
|
||||||
/** Foreground and background color indices, 0..255. */
|
/**
|
||||||
|
* Current foreground and background colors. Can either be a color index in [0,259] or a truecolor (24-bit) value.
|
||||||
|
* For a 24-bit value the top byte (0xff000000) is set.
|
||||||
|
*
|
||||||
|
* @see TextStyle
|
||||||
|
*/
|
||||||
int mForeColor, mBackColor;
|
int mForeColor, mBackColor;
|
||||||
|
|
||||||
/** Current TextStyle effect */
|
/** Current {@link TextStyle} effect. */
|
||||||
private int mEffect;
|
private int mEffect;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -219,7 +223,7 @@ public final class TerminalEmulator {
|
|||||||
*/
|
*/
|
||||||
private int mScrollCounter = 0;
|
private int mScrollCounter = 0;
|
||||||
|
|
||||||
private int mUtf8ToFollow, mUtf8Index;
|
private byte mUtf8ToFollow, mUtf8Index;
|
||||||
private final byte[] mUtf8InputBuffer = new byte[4];
|
private final byte[] mUtf8InputBuffer = new byte[4];
|
||||||
|
|
||||||
public final TerminalColors mColors = new TerminalColors();
|
public final TerminalColors mColors = new TerminalColors();
|
||||||
@@ -295,8 +299,7 @@ public final class TerminalEmulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mouseButton
|
* @param mouseButton one of the MOUSE_* constants of this class.
|
||||||
* one of the MOUSE_* constants of this class.
|
|
||||||
*/
|
*/
|
||||||
public void sendMouseEvent(int mouseButton, int column, int row, boolean pressed) {
|
public void sendMouseEvent(int mouseButton, int column, int row, boolean pressed) {
|
||||||
if (mouseButton == MOUSE_LEFT_BUTTON_MOVED && !isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT)) {
|
if (mouseButton == MOUSE_LEFT_BUTTON_MOVED && !isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT)) {
|
||||||
@@ -308,7 +311,7 @@ public final class TerminalEmulator {
|
|||||||
// Clip to screen, and clip to the limits of 8-bit data.
|
// Clip to screen, and clip to the limits of 8-bit data.
|
||||||
boolean out_of_bounds = column < 1 || row < 1 || column > mColumns || row > mRows || column > 255 - 32 || row > 255 - 32;
|
boolean out_of_bounds = column < 1 || row < 1 || column > mColumns || row > mRows || column > 255 - 32 || row > 255 - 32;
|
||||||
if (!out_of_bounds) {
|
if (!out_of_bounds) {
|
||||||
byte[] data = { '\033', '[', 'M', (byte) (32 + mouseButton), (byte) (32 + column), (byte) (32 + row) };
|
byte[] data = {'\033', '[', 'M', (byte) (32 + mouseButton), (byte) (32 + column), (byte) (32 + row)};
|
||||||
mSession.write(data, 0, data.length);
|
mSession.write(data, 0, data.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,7 +345,7 @@ public final class TerminalEmulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void resizeScreen() {
|
private void resizeScreen() {
|
||||||
final int[] cursor = { mCursorCol, mCursorRow };
|
final int[] cursor = {mCursorCol, mCursorRow};
|
||||||
int newTotalRows = (mScreen == mAltBuffer) ? mRows : mMainBuffer.mTotalRows;
|
int newTotalRows = (mScreen == mAltBuffer) ? mRows : mMainBuffer.mTotalRows;
|
||||||
mScreen.resize(mColumns, mRows, newTotalRows, cursor, getStyle(), isAlternateBufferActive());
|
mScreen.resize(mColumns, mRows, newTotalRows, cursor, getStyle(), isAlternateBufferActive());
|
||||||
mCursorCol = cursor[0];
|
mCursorCol = cursor[0];
|
||||||
@@ -391,10 +394,8 @@ public final class TerminalEmulator {
|
|||||||
/**
|
/**
|
||||||
* Accept bytes (typically from the pseudo-teletype) and process them.
|
* Accept bytes (typically from the pseudo-teletype) and process them.
|
||||||
*
|
*
|
||||||
* @param buffer
|
* @param buffer a byte array containing the bytes to be processed
|
||||||
* a byte array containing the bytes to be processed
|
* @param length the number of bytes in the array to process
|
||||||
* @param length
|
|
||||||
* the number of bytes in the array to process
|
|
||||||
*/
|
*/
|
||||||
public void append(byte[] buffer, int length) {
|
public void append(byte[] buffer, int length) {
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
@@ -425,7 +426,11 @@ public final class TerminalEmulator {
|
|||||||
processCodePoint(/* escape (hexadecimal=0x1B, octal=033): */27);
|
processCodePoint(/* escape (hexadecimal=0x1B, octal=033): */27);
|
||||||
processCodePoint((codePoint & 0x7F) + 0x40);
|
processCodePoint((codePoint & 0x7F) + 0x40);
|
||||||
} else {
|
} else {
|
||||||
if (Character.UNASSIGNED == Character.getType(codePoint)) codePoint = UNICODE_REPLACEMENT_CHAR;
|
switch (Character.getType(codePoint)) {
|
||||||
|
case Character.UNASSIGNED:
|
||||||
|
case Character.SURROGATE:
|
||||||
|
codePoint = UNICODE_REPLACEMENT_CHAR;
|
||||||
|
}
|
||||||
processCodePoint(codePoint);
|
processCodePoint(codePoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -471,15 +476,27 @@ public final class TerminalEmulator {
|
|||||||
else
|
else
|
||||||
mSession.onBell();
|
mSession.onBell();
|
||||||
break;
|
break;
|
||||||
case 8: // BS
|
case 8: // Backspace (BS, ^H).
|
||||||
setCursorCol(Math.max(mLeftMargin, mCursorCol - 1));
|
if (mLeftMargin == mCursorCol) {
|
||||||
break;
|
// Jump to previous line if it was auto-wrapped.
|
||||||
case 9: // Horizontal tab - move to next tab stop, but not past edge of screen
|
int previousRow = mCursorRow - 1;
|
||||||
int nextTabStop = nextTabStop(1);
|
if (previousRow >= 0 && mScreen.getLineWrap(previousRow)) {
|
||||||
while (mCursorCol < nextTabStop) {
|
mScreen.clearLineWrap(previousRow);
|
||||||
// Emit newlines to get background color right.
|
setCursorRowCol(previousRow, mRightMargin - 1);
|
||||||
processCodePoint(' ');
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
setCursorCol(mCursorCol - 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 9: // Horizontal tab (HT, \t) - move to next tab stop, but not past edge of screen
|
||||||
|
// XXX: Should perhaps use color if writing to new cells. Try with
|
||||||
|
// printf "\033[41m\tXX\033[0m\n"
|
||||||
|
// The OSX Terminal.app colors the spaces from the tab red, but xterm does not.
|
||||||
|
// Note that Terminal.app only colors on new cells, in e.g.
|
||||||
|
// printf "\033[41m\t\r\033[42m\tXX\033[0m\n"
|
||||||
|
// the first cells are created with a red background, but when tabbing over
|
||||||
|
// them again with a green background they are not overwritten.
|
||||||
|
mCursorCol = nextTabStop(1);
|
||||||
break;
|
break;
|
||||||
case 10: // Line feed (LF, \n).
|
case 10: // Line feed (LF, \n).
|
||||||
case 11: // Vertical tab (VT, \v).
|
case 11: // Vertical tab (VT, \v).
|
||||||
@@ -599,7 +616,7 @@ public final class TerminalEmulator {
|
|||||||
int left = Math.min(getArg(argIndex++, 1, true) + effectiveLeftMargin, effectiveRightMargin + 1);
|
int left = Math.min(getArg(argIndex++, 1, true) + effectiveLeftMargin, effectiveRightMargin + 1);
|
||||||
int bottom = Math.min(getArg(argIndex++, mRows, true) + effectiveTopMargin, effectiveBottomMargin);
|
int bottom = Math.min(getArg(argIndex++, mRows, true) + effectiveTopMargin, effectiveBottomMargin);
|
||||||
int right = Math.min(getArg(argIndex, mColumns, true) + effectiveLeftMargin, effectiveRightMargin);
|
int right = Math.min(getArg(argIndex, mColumns, true) + effectiveLeftMargin, effectiveRightMargin);
|
||||||
int style = getStyle();
|
long style = getStyle();
|
||||||
for (int row = top - 1; row < bottom; row++)
|
for (int row = top - 1; row < bottom; row++)
|
||||||
for (int col = left - 1; col < right; col++)
|
for (int col = left - 1; col < right; col++)
|
||||||
if (!selective || (TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)
|
if (!selective || (TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)
|
||||||
@@ -611,7 +628,7 @@ public final class TerminalEmulator {
|
|||||||
case 't': // "${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${ATTRIBUTES}$t"
|
case 't': // "${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${ATTRIBUTES}$t"
|
||||||
// Reverse attributes in rectangular area (DECRARA - http://www.vt100.net/docs/vt510-rm/DECRARA).
|
// Reverse attributes in rectangular area (DECRARA - http://www.vt100.net/docs/vt510-rm/DECRARA).
|
||||||
boolean reverse = b == 't';
|
boolean reverse = b == 't';
|
||||||
// FIXME: "coordinates of the rectangular area are affected by the setting of origin mode (DECOM)".s
|
// FIXME: "coordinates of the rectangular area are affected by the setting of origin mode (DECOM)".
|
||||||
int top = Math.min(getArg(0, 1, true) - 1, effectiveBottomMargin) + effectiveTopMargin;
|
int top = Math.min(getArg(0, 1, true) - 1, effectiveBottomMargin) + effectiveTopMargin;
|
||||||
int left = Math.min(getArg(1, 1, true) - 1, effectiveRightMargin) + effectiveLeftMargin;
|
int left = Math.min(getArg(1, 1, true) - 1, effectiveRightMargin) + effectiveLeftMargin;
|
||||||
int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin;
|
int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin;
|
||||||
@@ -882,7 +899,8 @@ public final class TerminalEmulator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (LOG_ESCAPE_SEQUENCES) Log.e(EmulatorDebug.LOG_TAG, "Unrecognized device control string: " + dcs);
|
if (LOG_ESCAPE_SEQUENCES)
|
||||||
|
Log.e(EmulatorDebug.LOG_TAG, "Unrecognized device control string: " + dcs);
|
||||||
}
|
}
|
||||||
finishSequence();
|
finishSequence();
|
||||||
}
|
}
|
||||||
@@ -908,8 +926,9 @@ public final class TerminalEmulator {
|
|||||||
/** Process byte while in the {@link #ESC_CSI_QUESTIONMARK} escape state. */
|
/** Process byte while in the {@link #ESC_CSI_QUESTIONMARK} escape state. */
|
||||||
private void doCsiQuestionMark(int b) {
|
private void doCsiQuestionMark(int b) {
|
||||||
switch (b) {
|
switch (b) {
|
||||||
case 'J': // Selective erase in display (DECSED - http://www.vt100.net/docs/vt510-rm/DECSED).
|
case 'J': // Selective erase in display (DECSED) - http://www.vt100.net/docs/vt510-rm/DECSED.
|
||||||
case 'K': // Selective erase in line (DECSEL - http://vt100.net/docs/vt510-rm/DECSEL).
|
case 'K': // Selective erase in line (DECSEL) - http://vt100.net/docs/vt510-rm/DECSEL.
|
||||||
|
mAboutToAutoWrap = false;
|
||||||
int fillChar = ' ';
|
int fillChar = ' ';
|
||||||
int startCol = -1;
|
int startCol = -1;
|
||||||
int startRow = -1;
|
int startRow = -1;
|
||||||
@@ -939,7 +958,7 @@ public final class TerminalEmulator {
|
|||||||
unknownSequence(b);
|
unknownSequence(b);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
int style = getStyle();
|
long style = getStyle();
|
||||||
for (int row = startRow; row < endRow; row++) {
|
for (int row = startRow; row < endRow; row++) {
|
||||||
for (int col = startCol; col < endCol; col++) {
|
for (int col = startCol; col < endCol; col++) {
|
||||||
if ((TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)
|
if ((TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)
|
||||||
@@ -1070,7 +1089,8 @@ public final class TerminalEmulator {
|
|||||||
// Check if buffer size needs to be updated:
|
// Check if buffer size needs to be updated:
|
||||||
if (resized) resizeScreen();
|
if (resized) resizeScreen();
|
||||||
// Clear new screen if alt buffer:
|
// Clear new screen if alt buffer:
|
||||||
if (newScreen == mAltBuffer) newScreen.blockSet(0, 0, mColumns, mRows, ' ', getStyle());
|
if (newScreen == mAltBuffer)
|
||||||
|
newScreen.blockSet(0, 0, mColumns, mRows, ' ', getStyle());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1230,6 +1250,11 @@ public final class TerminalEmulator {
|
|||||||
mScreen.blockSet(mRightMargin - 1, mTopMargin, 1, rows, ' ', TextStyle.encode(mForeColor, mBackColor, 0));
|
mScreen.blockSet(mRightMargin - 1, mTopMargin, 1, rows, ' ', TextStyle.encode(mForeColor, mBackColor, 0));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'c': // RIS - Reset to Initial State (http://vt100.net/docs/vt510-rm/RIS).
|
||||||
|
reset();
|
||||||
|
blockClear(0, 0, mColumns, mRows);
|
||||||
|
setCursorPosition(0, 0);
|
||||||
|
break;
|
||||||
case 'D': // INDEX
|
case 'D': // INDEX
|
||||||
doLinefeed();
|
doLinefeed();
|
||||||
break;
|
break;
|
||||||
@@ -1322,9 +1347,8 @@ public final class TerminalEmulator {
|
|||||||
continueSequence(ESC_CSI_ARGS_ASTERIX);
|
continueSequence(ESC_CSI_ARGS_ASTERIX);
|
||||||
break;
|
break;
|
||||||
case '@': {
|
case '@': {
|
||||||
// ESC [ Pn @ - ICH Insert Characters.
|
// "CSI{n}@" - Insert ${n} space characters (ICH) - http://www.vt100.net/docs/vt510-rm/ICH.
|
||||||
// "This control function inserts one or more space (SP) characters starting at the cursor position."
|
mAboutToAutoWrap = false;
|
||||||
// http://www.vt100.net/docs/vt510-rm/ICH
|
|
||||||
int columnsAfterCursor = mColumns - mCursorCol;
|
int columnsAfterCursor = mColumns - mCursorCol;
|
||||||
int spacesToInsert = Math.min(getArg0(1), columnsAfterCursor);
|
int spacesToInsert = Math.min(getArg0(1), columnsAfterCursor);
|
||||||
int charsToMove = columnsAfterCursor - spacesToInsert;
|
int charsToMove = columnsAfterCursor - spacesToInsert;
|
||||||
@@ -1361,7 +1385,7 @@ public final class TerminalEmulator {
|
|||||||
case 'I': // Cursor Horizontal Forward Tabulation (CHT). Move the active position n tabs forward.
|
case 'I': // Cursor Horizontal Forward Tabulation (CHT). Move the active position n tabs forward.
|
||||||
setCursorCol(nextTabStop(getArg0(1)));
|
setCursorCol(nextTabStop(getArg0(1)));
|
||||||
break;
|
break;
|
||||||
case 'J': // ESC [ Pn J - ED - Erase in Display
|
case 'J': // "${CSI}${0,1,2}J" - Erase in Display (ED)
|
||||||
// ED ignores the scrolling margins.
|
// ED ignores the scrolling margins.
|
||||||
switch (getArg0(0)) {
|
switch (getArg0(0)) {
|
||||||
case 0: // Erase from the active position to the end of the screen, inclusive (default).
|
case 0: // Erase from the active position to the end of the screen, inclusive (default).
|
||||||
@@ -1378,8 +1402,9 @@ public final class TerminalEmulator {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
unknownSequence(b);
|
unknownSequence(b);
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
mAboutToAutoWrap = false;
|
||||||
break;
|
break;
|
||||||
case 'K': // "CSI{n}K" - Erase in line (EL).
|
case 'K': // "CSI{n}K" - Erase in line (EL).
|
||||||
switch (getArg0(0)) {
|
switch (getArg0(0)) {
|
||||||
@@ -1394,8 +1419,9 @@ public final class TerminalEmulator {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
unknownSequence(b);
|
unknownSequence(b);
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
mAboutToAutoWrap = false;
|
||||||
break;
|
break;
|
||||||
case 'L': // "${CSI}{N}L" - insert ${N} lines (IL).
|
case 'L': // "${CSI}{N}L" - insert ${N} lines (IL).
|
||||||
{
|
{
|
||||||
@@ -1408,6 +1434,7 @@ public final class TerminalEmulator {
|
|||||||
break;
|
break;
|
||||||
case 'M': // "${CSI}${N}M" - delete N lines (DL).
|
case 'M': // "${CSI}${N}M" - delete N lines (DL).
|
||||||
{
|
{
|
||||||
|
mAboutToAutoWrap = false;
|
||||||
int linesAfterCursor = mBottomMargin - mCursorRow;
|
int linesAfterCursor = mBottomMargin - mCursorRow;
|
||||||
int linesToDelete = Math.min(getArg0(1), linesAfterCursor);
|
int linesToDelete = Math.min(getArg0(1), linesAfterCursor);
|
||||||
int linesToMove = linesAfterCursor - linesToDelete;
|
int linesToMove = linesAfterCursor - linesToDelete;
|
||||||
@@ -1422,6 +1449,7 @@ public final class TerminalEmulator {
|
|||||||
// As characters are deleted, the remaining characters between the cursor and right margin move to the left.
|
// As characters are deleted, the remaining characters between the cursor and right margin move to the left.
|
||||||
// Character attributes move with the characters. The terminal adds blank spaces with no visual character
|
// Character attributes move with the characters. The terminal adds blank spaces with no visual character
|
||||||
// attributes at the right margin. DCH has no effect outside the scrolling margins."
|
// attributes at the right margin. DCH has no effect outside the scrolling margins."
|
||||||
|
mAboutToAutoWrap = false;
|
||||||
int cellsAfterCursor = mColumns - mCursorCol;
|
int cellsAfterCursor = mColumns - mCursorCol;
|
||||||
int cellsToDelete = Math.min(getArg0(1), cellsAfterCursor);
|
int cellsToDelete = Math.min(getArg0(1), cellsAfterCursor);
|
||||||
int cellsToMove = cellsAfterCursor - cellsToDelete;
|
int cellsToMove = cellsAfterCursor - cellsToDelete;
|
||||||
@@ -1444,7 +1472,7 @@ public final class TerminalEmulator {
|
|||||||
final int linesToScrollArg = getArg0(1);
|
final int linesToScrollArg = getArg0(1);
|
||||||
final int linesBetweenTopAndBottomMargins = mBottomMargin - mTopMargin;
|
final int linesBetweenTopAndBottomMargins = mBottomMargin - mTopMargin;
|
||||||
final int linesToScroll = Math.min(linesBetweenTopAndBottomMargins, linesToScrollArg);
|
final int linesToScroll = Math.min(linesBetweenTopAndBottomMargins, linesToScrollArg);
|
||||||
mScreen.blockCopy(0, mTopMargin, mColumns, linesBetweenTopAndBottomMargins - linesToScroll, 0, linesToScroll);
|
mScreen.blockCopy(0, mTopMargin, mColumns, linesBetweenTopAndBottomMargins - linesToScroll, 0, mTopMargin + linesToScroll);
|
||||||
blockClear(0, mTopMargin, mColumns, linesToScroll);
|
blockClear(0, mTopMargin, mColumns, linesToScroll);
|
||||||
} else {
|
} else {
|
||||||
// "${CSI}${func};${startx};${starty};${firstrow};${lastrow}T" - initiate highlight mouse tracking.
|
// "${CSI}${func};${startx};${starty};${firstrow};${lastrow}T" - initiate highlight mouse tracking.
|
||||||
@@ -1452,6 +1480,7 @@ public final class TerminalEmulator {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'X': // "${CSI}${N}X" - Erase ${N:=1} character(s) (ECH). FIXME: Clears character attributes?
|
case 'X': // "${CSI}${N}X" - Erase ${N:=1} character(s) (ECH). FIXME: Clears character attributes?
|
||||||
|
mAboutToAutoWrap = false;
|
||||||
mScreen.blockSet(mCursorCol, mCursorRow, Math.min(getArg0(1), mColumns - mCursorCol), 1, ' ', getStyle());
|
mScreen.blockSet(mCursorCol, mCursorRow, Math.min(getArg0(1), mColumns - mCursorCol), 1, ' ', getStyle());
|
||||||
break;
|
break;
|
||||||
case 'Z': // Cursor Backward Tabulation (CBT). Move the active position n tabs backward.
|
case 'Z': // Cursor Backward Tabulation (CBT). Move the active position n tabs backward.
|
||||||
@@ -1517,7 +1546,7 @@ public final class TerminalEmulator {
|
|||||||
switch (getArg0(0)) {
|
switch (getArg0(0)) {
|
||||||
case 5: // Device status report (DSR):
|
case 5: // Device status report (DSR):
|
||||||
// Answer is ESC [ 0 n (Terminal OK).
|
// Answer is ESC [ 0 n (Terminal OK).
|
||||||
byte[] dsr = { (byte) 27, (byte) '[', (byte) '0', (byte) 'n' };
|
byte[] dsr = {(byte) 27, (byte) '[', (byte) '0', (byte) 'n'};
|
||||||
mSession.write(dsr, 0, dsr.length);
|
mSession.write(dsr, 0, dsr.length);
|
||||||
break;
|
break;
|
||||||
case 6: // Cursor position report (CPR):
|
case 6: // Cursor position report (CPR):
|
||||||
@@ -1663,11 +1692,10 @@ public final class TerminalEmulator {
|
|||||||
} else if (code >= 30 && code <= 37) {
|
} else if (code >= 30 && code <= 37) {
|
||||||
mForeColor = code - 30;
|
mForeColor = code - 30;
|
||||||
} else if (code == 38 || code == 48) {
|
} else if (code == 38 || code == 48) {
|
||||||
// ISO-8613-3 controls to set foreground (38) or background (48) colors.
|
// Extended set foreground(38)/background (48) color.
|
||||||
// P_s = (38|48) ; 2 ; P_r ; P_g ; P_b => Set to RGB value in range (0-255).
|
// This is followed by either "2;$R;$G;$B" to set a 24-bit color or
|
||||||
// P_s = (38|48) ; 5 ; P_s => Set to indexed color.
|
// "5;$INDEX" to set an indexed color.
|
||||||
if (i + 2 <= mArgIndex) {
|
if (i + 2 > mArgIndex) continue;
|
||||||
int color = -1;
|
|
||||||
int firstArg = mArgs[i + 1];
|
int firstArg = mArgs[i + 1];
|
||||||
if (firstArg == 2) {
|
if (firstArg == 2) {
|
||||||
if (i + 4 > mArgIndex) {
|
if (i + 4 > mArgIndex) {
|
||||||
@@ -1677,18 +1705,18 @@ public final class TerminalEmulator {
|
|||||||
if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) {
|
if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) {
|
||||||
finishSequenceAndLogError("Invalid RGB: " + red + "," + green + "," + blue);
|
finishSequenceAndLogError("Invalid RGB: " + red + "," + green + "," + blue);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Implement 24 bit color.
|
int argbColor = 0xff000000 | (red << 16) | (green << 8) | blue;
|
||||||
finishSequenceAndLogError("Unimplemented RGB: " + red + "," + green + "," + blue);
|
if (code == 38) {
|
||||||
|
mForeColor = argbColor;
|
||||||
|
} else {
|
||||||
|
mBackColor = argbColor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i += 4; // "2;P_r;P_g;P_r"
|
i += 4; // "2;P_r;P_g;P_r"
|
||||||
}
|
}
|
||||||
} else if (firstArg == 5) {
|
} else if (firstArg == 5) {
|
||||||
color = mArgs[i + 2];
|
int color = mArgs[i + 2];
|
||||||
i += 2; // "5;P_s"
|
i += 2; // "5;P_s"
|
||||||
} else {
|
|
||||||
finishSequenceAndLogError("Invalid ISO-8613-3 SGR first argument: " + firstArg);
|
|
||||||
}
|
|
||||||
if (i != -1) {
|
|
||||||
if (color >= 0 && color < TextStyle.NUM_INDEXED_COLORS) {
|
if (color >= 0 && color < TextStyle.NUM_INDEXED_COLORS) {
|
||||||
if (code == 38) {
|
if (code == 38) {
|
||||||
mForeColor = color;
|
mForeColor = color;
|
||||||
@@ -1698,7 +1726,8 @@ public final class TerminalEmulator {
|
|||||||
} else {
|
} else {
|
||||||
if (LOG_ESCAPE_SEQUENCES) Log.w(EmulatorDebug.LOG_TAG, "Invalid color index: " + color);
|
if (LOG_ESCAPE_SEQUENCES) Log.w(EmulatorDebug.LOG_TAG, "Invalid color index: " + color);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
finishSequenceAndLogError("Invalid ISO-8613-3 SGR first argument: " + firstArg);
|
||||||
}
|
}
|
||||||
} else if (code == 39) { // Set default foreground color.
|
} else if (code == 39) { // Set default foreground color.
|
||||||
mForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
mForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
||||||
@@ -1711,7 +1740,8 @@ public final class TerminalEmulator {
|
|||||||
} else if (code >= 100 && code <= 107) { // Bright background color (aixterm codes).
|
} else if (code >= 100 && code <= 107) { // Bright background color (aixterm codes).
|
||||||
mBackColor = code - 100 + 8;
|
mBackColor = code - 100 + 8;
|
||||||
} else {
|
} else {
|
||||||
if (LOG_ESCAPE_SEQUENCES) Log.w(EmulatorDebug.LOG_TAG, String.format("SGR unknown code %d", code));
|
if (LOG_ESCAPE_SEQUENCES)
|
||||||
|
Log.w(EmulatorDebug.LOG_TAG, String.format("SGR unknown code %d", code));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1779,7 +1809,7 @@ public final class TerminalEmulator {
|
|||||||
// and specification can be given in one control sequence, xterm can make more than one reply.
|
// and specification can be given in one control sequence, xterm can make more than one reply.
|
||||||
int colorIndex = -1;
|
int colorIndex = -1;
|
||||||
int parsingPairStart = -1;
|
int parsingPairStart = -1;
|
||||||
for (int i = 0;; i++) {
|
for (int i = 0; ; i++) {
|
||||||
boolean endOfInput = i == textParameter.length();
|
boolean endOfInput = i == textParameter.length();
|
||||||
char b = endOfInput ? ';' : textParameter.charAt(i);
|
char b = endOfInput ? ';' : textParameter.charAt(i);
|
||||||
if (b == ';') {
|
if (b == ';') {
|
||||||
@@ -1791,6 +1821,7 @@ public final class TerminalEmulator {
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
mColors.tryParseColor(colorIndex, textParameter.substring(parsingPairStart, i));
|
mColors.tryParseColor(colorIndex, textParameter.substring(parsingPairStart, i));
|
||||||
|
mSession.onColorsChanged();
|
||||||
colorIndex = -1;
|
colorIndex = -1;
|
||||||
parsingPairStart = -1;
|
parsingPairStart = -1;
|
||||||
}
|
}
|
||||||
@@ -1811,7 +1842,7 @@ public final class TerminalEmulator {
|
|||||||
case 12: // Set cursor color.
|
case 12: // Set cursor color.
|
||||||
int specialIndex = TextStyle.COLOR_INDEX_FOREGROUND + (value - 10);
|
int specialIndex = TextStyle.COLOR_INDEX_FOREGROUND + (value - 10);
|
||||||
int lastSemiIndex = 0;
|
int lastSemiIndex = 0;
|
||||||
for (int charIndex = 0;; charIndex++) {
|
for (int charIndex = 0; ; charIndex++) {
|
||||||
boolean endOfInput = charIndex == textParameter.length();
|
boolean endOfInput = charIndex == textParameter.length();
|
||||||
if (endOfInput || textParameter.charAt(charIndex) == ';') {
|
if (endOfInput || textParameter.charAt(charIndex) == ';') {
|
||||||
try {
|
try {
|
||||||
@@ -1826,9 +1857,11 @@ public final class TerminalEmulator {
|
|||||||
+ String.format(Locale.US, "%04x", b) + bellOrStringTerminator);
|
+ String.format(Locale.US, "%04x", b) + bellOrStringTerminator);
|
||||||
} else {
|
} else {
|
||||||
mColors.tryParseColor(specialIndex, colorSpec);
|
mColors.tryParseColor(specialIndex, colorSpec);
|
||||||
|
mSession.onColorsChanged();
|
||||||
}
|
}
|
||||||
specialIndex++;
|
specialIndex++;
|
||||||
if (endOfInput || (specialIndex > TextStyle.COLOR_INDEX_CURSOR) || ++charIndex >= textParameter.length()) break;
|
if (endOfInput || (specialIndex > TextStyle.COLOR_INDEX_CURSOR) || ++charIndex >= textParameter.length())
|
||||||
|
break;
|
||||||
lastSemiIndex = charIndex;
|
lastSemiIndex = charIndex;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
// Ignore.
|
// Ignore.
|
||||||
@@ -1852,14 +1885,16 @@ public final class TerminalEmulator {
|
|||||||
// parameters are given, the entire table will be reset.
|
// parameters are given, the entire table will be reset.
|
||||||
if (textParameter.isEmpty()) {
|
if (textParameter.isEmpty()) {
|
||||||
mColors.reset();
|
mColors.reset();
|
||||||
|
mSession.onColorsChanged();
|
||||||
} else {
|
} else {
|
||||||
int lastIndex = 0;
|
int lastIndex = 0;
|
||||||
for (int charIndex = 0;; charIndex++) {
|
for (int charIndex = 0; ; charIndex++) {
|
||||||
boolean endOfInput = charIndex == textParameter.length();
|
boolean endOfInput = charIndex == textParameter.length();
|
||||||
if (endOfInput || textParameter.charAt(charIndex) == ';') {
|
if (endOfInput || textParameter.charAt(charIndex) == ';') {
|
||||||
try {
|
try {
|
||||||
int colorToReset = Integer.parseInt(textParameter.substring(lastIndex, charIndex));
|
int colorToReset = Integer.parseInt(textParameter.substring(lastIndex, charIndex));
|
||||||
mColors.reset(colorToReset);
|
mColors.reset(colorToReset);
|
||||||
|
mSession.onColorsChanged();
|
||||||
if (endOfInput) break;
|
if (endOfInput) break;
|
||||||
charIndex++;
|
charIndex++;
|
||||||
lastIndex = charIndex;
|
lastIndex = charIndex;
|
||||||
@@ -1874,6 +1909,7 @@ public final class TerminalEmulator {
|
|||||||
case 111: // Reset background color.
|
case 111: // Reset background color.
|
||||||
case 112: // Reset cursor color.
|
case 112: // Reset cursor color.
|
||||||
mColors.reset(TextStyle.COLOR_INDEX_FOREGROUND + (value - 110));
|
mColors.reset(TextStyle.COLOR_INDEX_FOREGROUND + (value - 110));
|
||||||
|
mSession.onColorsChanged();
|
||||||
break;
|
break;
|
||||||
case 119: // Reset highlight color.
|
case 119: // Reset highlight color.
|
||||||
break;
|
break;
|
||||||
@@ -1892,7 +1928,7 @@ public final class TerminalEmulator {
|
|||||||
mScreen.blockSet(sx, sy, w, h, ' ', getStyle());
|
mScreen.blockSet(sx, sy, w, h, ' ', getStyle());
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getStyle() {
|
private long getStyle() {
|
||||||
return TextStyle.encode(mForeColor, mBackColor, mEffect);
|
return TextStyle.encode(mForeColor, mBackColor, mEffect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2045,8 +2081,7 @@ public final class TerminalEmulator {
|
|||||||
/**
|
/**
|
||||||
* Send a Unicode code point to the screen.
|
* Send a Unicode code point to the screen.
|
||||||
*
|
*
|
||||||
* @param codePoint
|
* @param codePoint The code point of the character to display
|
||||||
* The code point of the character to display
|
|
||||||
*/
|
*/
|
||||||
private void emitCodePoint(int codePoint) {
|
private void emitCodePoint(int codePoint) {
|
||||||
if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) {
|
if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) {
|
||||||
@@ -2156,8 +2191,10 @@ public final class TerminalEmulator {
|
|||||||
|
|
||||||
final boolean autoWrap = isDecsetInternalBitSet(DECSET_BIT_AUTOWRAP);
|
final boolean autoWrap = isDecsetInternalBitSet(DECSET_BIT_AUTOWRAP);
|
||||||
final int displayWidth = WcWidth.width(codePoint);
|
final int displayWidth = WcWidth.width(codePoint);
|
||||||
|
final boolean cursorInLastColumn = mCursorCol == mRightMargin - 1;
|
||||||
|
|
||||||
if (autoWrap && (mCursorCol == mRightMargin - 1 && ((mAboutToAutoWrap && displayWidth == 1) || displayWidth == 2))) {
|
if (autoWrap) {
|
||||||
|
if (cursorInLastColumn && ((mAboutToAutoWrap && displayWidth == 1) || displayWidth == 2)) {
|
||||||
mScreen.setLineWrap(mCursorRow);
|
mScreen.setLineWrap(mCursorRow);
|
||||||
mCursorCol = mLeftMargin;
|
mCursorCol = mLeftMargin;
|
||||||
if (mCursorRow + 1 < mBottomMargin) {
|
if (mCursorRow + 1 < mBottomMargin) {
|
||||||
@@ -2166,17 +2203,24 @@ public final class TerminalEmulator {
|
|||||||
scrollDownOneLine();
|
scrollDownOneLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (cursorInLastColumn && displayWidth == 2) {
|
||||||
|
// The behaviour when a wide character is output with cursor in the last column when
|
||||||
|
// autowrap is disabled is not obvious - it's ignored here.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (mInsertMode && displayWidth > 0) {
|
if (mInsertMode && displayWidth > 0) {
|
||||||
// Move character to right one space.
|
// Move character to right one space.
|
||||||
int destCol = mCursorCol + displayWidth;
|
int destCol = mCursorCol + displayWidth;
|
||||||
if (destCol < mRightMargin) mScreen.blockCopy(mCursorCol, mCursorRow, mRightMargin - destCol, 1, destCol, mCursorRow);
|
if (destCol < mRightMargin)
|
||||||
|
mScreen.blockCopy(mCursorCol, mCursorRow, mRightMargin - destCol, 1, destCol, mCursorRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
int offsetDueToCombiningChar = ((displayWidth <= 0 && mCursorCol > 0 && !mAboutToAutoWrap) ? 1 : 0);
|
int offsetDueToCombiningChar = ((displayWidth <= 0 && mCursorCol > 0 && !mAboutToAutoWrap) ? 1 : 0);
|
||||||
mScreen.setChar(mCursorCol - offsetDueToCombiningChar, mCursorRow, codePoint, getStyle());
|
mScreen.setChar(mCursorCol - offsetDueToCombiningChar, mCursorRow, codePoint, getStyle());
|
||||||
|
|
||||||
if (autoWrap && displayWidth > 0) mAboutToAutoWrap = (mCursorCol == mRightMargin - displayWidth);
|
if (autoWrap && displayWidth > 0)
|
||||||
|
mAboutToAutoWrap = (mCursorCol == mRightMargin - displayWidth);
|
||||||
|
|
||||||
mCursorCol = Math.min(mCursorCol + displayWidth, mRightMargin - 1);
|
mCursorCol = Math.min(mCursorCol + displayWidth, mRightMargin - 1);
|
||||||
}
|
}
|
||||||
@@ -2241,6 +2285,7 @@ public final class TerminalEmulator {
|
|||||||
mUtf8Index = mUtf8ToFollow = 0;
|
mUtf8Index = mUtf8ToFollow = 0;
|
||||||
|
|
||||||
mColors.reset();
|
mColors.reset();
|
||||||
|
mSession.onColorsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSelectedText(int x1, int y1, int x2, int y2) {
|
public String getSelectedText(int x1, int y1, int x2, int y2) {
|
||||||
|
|||||||
@@ -23,4 +23,6 @@ public abstract class TerminalOutput {
|
|||||||
/** Notify the terminal client that a bell character (ASCII 7, bell, BEL, \a, ^G)) has been received. */
|
/** Notify the terminal client that a bell character (ASCII 7, bell, BEL, \a, ^G)) has been received. */
|
||||||
public abstract void onBell();
|
public abstract void onBell();
|
||||||
|
|
||||||
|
public abstract void onColorsChanged();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A row in a terminal, composed of a fixed number of cells.
|
* A row in a terminal, composed of a fixed number of cells.
|
||||||
*
|
* <p/>
|
||||||
* The text in the row is stored in a char[] array, {@link #mText}, for quick access during rendering.
|
* The text in the row is stored in a char[] array, {@link #mText}, for quick access during rendering.
|
||||||
*/
|
*/
|
||||||
public final class TerminalRow {
|
public final class TerminalRow {
|
||||||
@@ -20,13 +20,13 @@ public final class TerminalRow {
|
|||||||
/** If this row has been line wrapped due to text output at the end of line. */
|
/** If this row has been line wrapped due to text output at the end of line. */
|
||||||
boolean mLineWrap;
|
boolean mLineWrap;
|
||||||
/** The style bits of each cell in the row. See {@link TextStyle}. */
|
/** The style bits of each cell in the row. See {@link TextStyle}. */
|
||||||
final int[] mStyle;
|
final long[] mStyle;
|
||||||
|
|
||||||
/** Construct a blank row (containing only whitespace, ' ') with a specified style. */
|
/** Construct a blank row (containing only whitespace, ' ') with a specified style. */
|
||||||
public TerminalRow(int columns, int style) {
|
public TerminalRow(int columns, long style) {
|
||||||
mColumns = columns;
|
mColumns = columns;
|
||||||
mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)];
|
mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)];
|
||||||
mStyle = new int[columns];
|
mStyle = new long[columns];
|
||||||
clear(style);
|
clear(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ public final class TerminalRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean wideDisplayCharacterStartingAt(int column) {
|
private boolean wideDisplayCharacterStartingAt(int column) {
|
||||||
for (int currentCharIndex = 0, currentColumn = 0; currentCharIndex < mSpaceUsed;) {
|
for (int currentCharIndex = 0, currentColumn = 0; currentCharIndex < mSpaceUsed; ) {
|
||||||
char c = mText[currentCharIndex++];
|
char c = mText[currentCharIndex++];
|
||||||
int codePoint = Character.isHighSurrogate(c) ? Character.toCodePoint(c, mText[currentCharIndex++]) : c;
|
int codePoint = Character.isHighSurrogate(c) ? Character.toCodePoint(c, mText[currentCharIndex++]) : c;
|
||||||
int wcwidth = WcWidth.width(codePoint);
|
int wcwidth = WcWidth.width(codePoint);
|
||||||
@@ -112,14 +112,14 @@ public final class TerminalRow {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear(int style) {
|
public void clear(long style) {
|
||||||
Arrays.fill(mText, ' ');
|
Arrays.fill(mText, ' ');
|
||||||
Arrays.fill(mStyle, style);
|
Arrays.fill(mStyle, style);
|
||||||
mSpaceUsed = (short) mColumns;
|
mSpaceUsed = (short) mColumns;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26
|
// https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26
|
||||||
public void setChar(int columnToSet, int codePoint, int style) {
|
public void setChar(int columnToSet, int codePoint, long style) {
|
||||||
mStyle[columnToSet] = style;
|
mStyle[columnToSet] = style;
|
||||||
|
|
||||||
final int newCodePointDisplayWidth = WcWidth.width(codePoint);
|
final int newCodePointDisplayWidth = WcWidth.width(codePoint);
|
||||||
@@ -184,6 +184,7 @@ public final class TerminalRow {
|
|||||||
mSpaceUsed += javaCharDifference;
|
mSpaceUsed += javaCharDifference;
|
||||||
|
|
||||||
// Store char. A combining character is stored at the end of the existing contents so that it modifies them:
|
// 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));
|
Character.toChars(codePoint, text, oldStartOfColumnIndex + (newIsCombining ? oldCharactersUsedForColumn : 0));
|
||||||
|
|
||||||
if (oldCodePointDisplayWidth == 2 && newCodePointDisplayWidth == 1) {
|
if (oldCodePointDisplayWidth == 2 && newCodePointDisplayWidth == 1) {
|
||||||
@@ -224,7 +225,7 @@ public final class TerminalRow {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int getStyle(int column) {
|
public final long getStyle(int column) {
|
||||||
return mStyle[column];
|
return mStyle[column];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.system.ErrnoException;
|
||||||
|
import android.system.Os;
|
||||||
|
import android.system.OsConstants;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@@ -9,20 +17,15 @@ import java.lang.reflect.Field;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A terminal session, consisting of a process coupled to a terminal interface.
|
* A terminal session, consisting of a process coupled to a terminal interface.
|
||||||
* <p>
|
* <p/>
|
||||||
* The subprocess will be executed by the constructor, and when the size is made known by a call to
|
* The subprocess will be executed by the constructor, and when the size is made known by a call to
|
||||||
* {@link #updateSize(int, int)} terminal emulation will begin and threads will be spawned to handle the subprocess I/O.
|
* {@link #updateSize(int, int)} terminal emulation will begin and threads will be spawned to handle the subprocess I/O.
|
||||||
* All terminal emulation and callback methods will be performed on the main thread.
|
* All terminal emulation and callback methods will be performed on the main thread.
|
||||||
* <p>
|
* <p/>
|
||||||
* The child process may be exited forcefully by using the {@link #finishIfRunning()} method.
|
* The child process may be exited forcefully by using the {@link #finishIfRunning()} method.
|
||||||
*
|
* <p/>
|
||||||
* NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks!
|
* NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks!
|
||||||
*/
|
*/
|
||||||
public final class TerminalSession extends TerminalOutput {
|
public final class TerminalSession extends TerminalOutput {
|
||||||
@@ -38,6 +41,9 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
void onClipboardText(TerminalSession session, String text);
|
void onClipboardText(TerminalSession session, String text);
|
||||||
|
|
||||||
void onBell(TerminalSession session);
|
void onBell(TerminalSession session);
|
||||||
|
|
||||||
|
void onColorsChanged(TerminalSession session);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FileDescriptor wrapFileDescriptor(int fileDescriptor) {
|
private static FileDescriptor wrapFileDescriptor(int fileDescriptor) {
|
||||||
@@ -82,14 +88,17 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
/** Callback which gets notified when a session finishes or changes title. */
|
/** Callback which gets notified when a session finishes or changes title. */
|
||||||
final SessionChangedCallback mChangeCallback;
|
final SessionChangedCallback mChangeCallback;
|
||||||
|
|
||||||
/** The pid of the shell process or -1 if not running. */
|
/** The pid of the shell process. 0 if not started and -1 if finished running. */
|
||||||
int mShellPid;
|
int mShellPid;
|
||||||
int mShellExitStatus = -1;
|
|
||||||
|
/** The exit status of the shell process. Only valid if ${@link #mShellPid} is -1. */
|
||||||
|
int mShellExitStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The file descriptor referencing the master half of a pseudo-terminal pair, resulting from calling
|
* The file descriptor referencing the master half of a pseudo-terminal pair, resulting from calling
|
||||||
* {@link JNI#createSubprocess(String, String, String[], String[], int[])}.
|
* {@link JNI#createSubprocess(String, String, String[], String[], int[], int, int)}.
|
||||||
*/
|
*/
|
||||||
final int mTerminalFileDescriptor;
|
private int mTerminalFileDescriptor;
|
||||||
|
|
||||||
/** Set by the application for user identification of session, not by terminal. */
|
/** Set by the application for user identification of session, not by terminal. */
|
||||||
public String mSessionName;
|
public String mSessionName;
|
||||||
@@ -119,7 +128,7 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
// Negated signal.
|
// Negated signal.
|
||||||
exitDescription += " with signal " + (-exitCode);
|
exitDescription += " with signal " + (-exitCode);
|
||||||
}
|
}
|
||||||
exitDescription += "]";
|
exitDescription += " - press Enter to close]";
|
||||||
|
|
||||||
byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
|
byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
|
||||||
mEmulator.append(bytesToWrite, bytesToWrite.length);
|
mEmulator.append(bytesToWrite, bytesToWrite.length);
|
||||||
@@ -128,20 +137,26 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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) {
|
public TerminalSession(String shellPath, String cwd, String[] args, String[] env, SessionChangedCallback changeCallback) {
|
||||||
mChangeCallback = changeCallback;
|
mChangeCallback = changeCallback;
|
||||||
|
|
||||||
int[] processId = new int[1];
|
this.mShellPath = shellPath;
|
||||||
mTerminalFileDescriptor = JNI.createSubprocess(shellPath, cwd, args, env, processId);
|
this.mCwd = cwd;
|
||||||
mShellPid = processId[0];
|
this.mArgs = args;
|
||||||
|
this.mEnv = env;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Inform the attached pty of the new size and reflow or initialize the emulator. */
|
/** Inform the attached pty of the new size and reflow or initialize the emulator. */
|
||||||
public void updateSize(int columns, int rows) {
|
public void updateSize(int columns, int rows) {
|
||||||
JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns);
|
|
||||||
if (mEmulator == null) {
|
if (mEmulator == null) {
|
||||||
initializeEmulator(columns, rows);
|
initializeEmulator(columns, rows);
|
||||||
} else {
|
} else {
|
||||||
|
JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns);
|
||||||
mEmulator.resize(columns, rows);
|
mEmulator.resize(columns, rows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,13 +169,16 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
/**
|
/**
|
||||||
* Set the terminal emulator's window size and start terminal emulation.
|
* Set the terminal emulator's window size and start terminal emulation.
|
||||||
*
|
*
|
||||||
* @param columns
|
* @param columns The number of columns in the terminal window.
|
||||||
* The number of columns in the terminal window.
|
* @param rows The number of rows in the terminal window.
|
||||||
* @param rows
|
|
||||||
* The number of rows in the terminal window.
|
|
||||||
*/
|
*/
|
||||||
public void initializeEmulator(int columns, int rows) {
|
public void initializeEmulator(int columns, int rows) {
|
||||||
mEmulator = new TerminalEmulator(this, columns, rows, /* transcript= */5000);
|
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);
|
final FileDescriptor terminalFileDescriptorWrapped = wrapFileDescriptor(mTerminalFileDescriptor);
|
||||||
|
|
||||||
new Thread("TermSessionInputReader[pid=" + mShellPid + "]") {
|
new Thread("TermSessionInputReader[pid=" + mShellPid + "]") {
|
||||||
@@ -176,10 +194,6 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Ignore, just shutting down.
|
// Ignore, just shutting down.
|
||||||
} finally {
|
|
||||||
// Now wait for process exit:
|
|
||||||
int processExitCode = JNI.waitFor(mShellPid);
|
|
||||||
mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(MSG_PROCESS_EXITED, processExitCode));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start();
|
}.start();
|
||||||
@@ -199,12 +213,21 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start();
|
}.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 shell process. */
|
/** Write data to the shell process. */
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] data, int offset, int count) {
|
public void write(byte[] data, int offset, int count) {
|
||||||
mTerminalToProcessIOQueue.write(data, offset, count);
|
if (mShellPid > 0) mTerminalToProcessIOQueue.write(data, offset, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Write the Unicode code point to the terminal encoded in UTF-8. */
|
/** Write the Unicode code point to the terminal encoded in UTF-8. */
|
||||||
@@ -259,18 +282,14 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
notifyScreenUpdate();
|
notifyScreenUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Finish this terminal session by sending SIGKILL to the shell. */
|
||||||
* Finish this terminal session. Frees resources used by the terminal emulator and closes the attached
|
|
||||||
* <code>InputStream</code> and <code>OutputStream</code>.
|
|
||||||
*/
|
|
||||||
public void finishIfRunning() {
|
public void finishIfRunning() {
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
JNI.hangupProcessGroup(mShellPid);
|
try {
|
||||||
// Stop the reader and writer threads, and close the I/O streams. Note that
|
Os.kill(mShellPid, OsConstants.SIGKILL);
|
||||||
// cleanupResources() will be run later.
|
} catch (ErrnoException e) {
|
||||||
mTerminalToProcessIOQueue.close();
|
Log.w("termux", "Failed sending SIGKILL: " + e.getMessage());
|
||||||
mProcessToTerminalIOQueue.close();
|
}
|
||||||
JNI.close(mTerminalFileDescriptor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,4 +330,13 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
mChangeCallback.onBell(this);
|
mChangeCallback.onBell(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onColorsChanged() {
|
||||||
|
mChangeCallback.onColorsChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPid() {
|
||||||
|
return mShellPid;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes effects, foreground and background colors into a 32 bit integer, which are stored for each cell in a terminal
|
* Encodes effects, foreground and background colors into a 64 bit long, which are stored for each cell in a terminal
|
||||||
* row in {@link TerminalRow#mStyle}.
|
* row in {@link TerminalRow#mStyle}.
|
||||||
*
|
* <p/>
|
||||||
* The foreground and background colors take 9 bits each, leaving (32-9-9)=14 bits for effect flags. Using 9 for now
|
* The bit layout is:
|
||||||
* (the different CHARACTER_ATTRIBUTE_* bits).
|
* - 16 flags (11 currently used).
|
||||||
|
* - 24 for foreground color (only 9 first bits if a color index).
|
||||||
|
* - 24 for background color (only 9 first bits if a color index).
|
||||||
*/
|
*/
|
||||||
public final class TextStyle {
|
public final class TextStyle {
|
||||||
|
|
||||||
@@ -18,13 +20,17 @@ public final class TextStyle {
|
|||||||
public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6;
|
public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6;
|
||||||
/**
|
/**
|
||||||
* The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable.
|
* The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable.
|
||||||
*
|
* <p/>
|
||||||
* This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that
|
* This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that
|
||||||
* come after it as erasable from the screen.
|
* come after it as erasable from the screen.
|
||||||
*/
|
*/
|
||||||
public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7;
|
public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7;
|
||||||
/** Dim colors. Also known as faint or half intensity. */
|
/** Dim colors. Also known as faint or half intensity. */
|
||||||
public final static int CHARACTER_ATTRIBUTE_DIM = 1 << 8;
|
public final static int CHARACTER_ATTRIBUTE_DIM = 1 << 8;
|
||||||
|
/** If true (24-bit) color is used for the cell for foreground. */
|
||||||
|
private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND = 1 << 9;
|
||||||
|
/** If true (24-bit) color is used for the cell for foreground. */
|
||||||
|
private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND= 1 << 10;
|
||||||
|
|
||||||
public final static int COLOR_INDEX_FOREGROUND = 256;
|
public final static int COLOR_INDEX_FOREGROUND = 256;
|
||||||
public final static int COLOR_INDEX_BACKGROUND = 257;
|
public final static int COLOR_INDEX_BACKGROUND = 257;
|
||||||
@@ -34,22 +40,47 @@ public final class TextStyle {
|
|||||||
public final static int NUM_INDEXED_COLORS = 259;
|
public final static int NUM_INDEXED_COLORS = 259;
|
||||||
|
|
||||||
/** Normal foreground and background colors and no effects. */
|
/** Normal foreground and background colors and no effects. */
|
||||||
final static int NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0);
|
final static long NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0);
|
||||||
|
|
||||||
static int encode(int foreColor, int backColor, int effect) {
|
static long encode(int foreColor, int backColor, int effect) {
|
||||||
return ((effect & 0b111111111) << 18) | ((foreColor & 0b111111111) << 9) | (backColor & 0b111111111);
|
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(int encodedColor) {
|
return result;
|
||||||
return (encodedColor >> 9) & 0b111111111;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int decodeBackColor(int encodedColor) {
|
public static int decodeForeColor(long style) {
|
||||||
return encodedColor & 0b111111111;
|
if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND) == 0) {
|
||||||
|
return (int) ((style >>> 40) & 0b111111111L);
|
||||||
|
} else {
|
||||||
|
return 0xff000000 | (int) ((style >>> 40) & 0x00ffffffL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int decodeEffect(int encodedColor) {
|
}
|
||||||
return (encodedColor >> 18) & 0b111111111;
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +1,452 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* wcwidth() implementation from http://git.musl-libc.org/cgit/musl/tree/src/ctype
|
* Implementation of wcwidth(3) for Unicode 9.
|
||||||
*
|
*
|
||||||
* Modified to return 0 instead of -1.
|
* Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters.
|
||||||
*/
|
*/
|
||||||
public final class WcWidth {
|
public final class WcWidth {
|
||||||
|
|
||||||
private static final short table[] = { 16, 16, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 16, 32, 16, 16, 16, 33, 34, 35, 36, 37, 38,
|
// From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py
|
||||||
39, 16, 16, 40, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 41, 42, 16, 16, 43, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
// t commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02):
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
private static final int[][] ZERO_WIDTH = {
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x0300, 0x036f}, // Combining Grave Accent ..Combining Latin Small Le
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 44, 16, 45, 46, 47, 48, 16, 16, 16, 16, 16,
|
{0x0483, 0x0489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x0591, 0x05bd}, // Hebrew Accent Etnahta ..Hebrew Point Meteg
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x05bf, 0x05bf}, // Hebrew Point Rafe ..Hebrew Point Rafe
|
||||||
49, 16, 16, 50, 51, 16, 52, 16, 16, 16, 16, 16, 16, 16, 16, 53, 16, 16, 16, 16, 16, 54, 55, 16, 16, 16, 16, 56, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x05c1, 0x05c2}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x05c4, 0x05c5}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x05c7, 0x05c7}, // Hebrew Point Qamats Qata..Hebrew Point Qamats Qata
|
||||||
16, 16, 16, 16, 16, 57, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x0610, 0x061a}, // Arabic Sign Sallallahou ..Arabic Small Kasra
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x064b, 0x065f}, // Arabic Fathatan ..Arabic Wavy Hamza Below
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 58, 59, 16, 16, 16, 16, 16, 16,
|
{0x0670, 0x0670}, // Arabic Letter Superscrip..Arabic Letter Superscrip
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x06d6, 0x06dc}, // Arabic Small High Ligatu..Arabic Small High Seen
|
||||||
16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255,
|
{0x06df, 0x06e4}, // Arabic Small High Rounde..Arabic Small High Madda
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
{0x06e7, 0x06e8}, // Arabic Small High Yeh ..Arabic Small High Noon
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x06ea, 0x06ed}, // Arabic Empty Centre Low ..Arabic Small Low Meem
|
||||||
248, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, 255, 255, 191, 182, 0, 0, 0,
|
{0x0711, 0x0711}, // Syriac Letter Superscrip..Syriac Letter Superscrip
|
||||||
0, 0, 0, 0, 31, 0, 255, 7, 0, 0, 0, 0, 0, 248, 255, 255, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 191, 159, 61, 0, 0, 0, 128, 2, 0, 0, 0,
|
{0x0730, 0x074a}, // Syriac Pthaha Above ..Syriac Barrekh
|
||||||
255, 255, 255, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 255, 1, 0, 0, 0, 0, 0, 0, 248, 15, 0, 0, 0, 192, 251, 239, 62, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0,
|
{0x07a6, 0x07b0}, // Thaana Abafili ..Thaana Sukun
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 255, 255, 127, 7, 0, 0, 0, 0, 0, 0, 20, 254, 33, 254, 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 16, 30, 32, 0,
|
{0x07eb, 0x07f3}, // Nko Combining Sh||t High..Nko Combining Double Dot
|
||||||
0, 12, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 16, 134, 57, 2, 0, 0, 0, 35, 0, 6, 0, 0, 0, 0, 0, 0, 16, 190, 33, 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 144,
|
{0x0816, 0x0819}, // Samaritan Mark In ..Samaritan Mark Dagesh
|
||||||
30, 32, 64, 0, 12, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 193, 61, 96, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x081b, 0x0823}, // Samaritan Mark Epentheti..Samaritan Vowel Sign A
|
||||||
0, 0, 144, 64, 48, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 32, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 92, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x0825, 0x0827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U
|
||||||
0, 0, 0, 242, 7, 128, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 27, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 160, 2, 0, 0, 0, 0, 0, 0, 254,
|
{0x0829, 0x082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa
|
||||||
127, 223, 224, 255, 254, 255, 255, 255, 31, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 253, 102, 0, 0, 0, 195, 1, 0, 30, 0, 100, 32, 0, 32, 0, 0,
|
{0x0859, 0x085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0,
|
{0x08d4, 0x08e1}, // (nil) ..
|
||||||
28, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 176, 63, 64, 254, 15, 32, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x08e3, 0x0902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara
|
||||||
0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135, 1, 4, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x093a, 0x093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe
|
||||||
128, 1, 0, 0, 0, 0, 0, 0, 64, 127, 229, 31, 248, 159, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 208, 23, 4, 0, 0, 0, 0,
|
{0x093c, 0x093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta
|
||||||
248, 15, 0, 3, 0, 0, 0, 60, 11, 0, 0, 0, 0, 0, 0, 64, 163, 3, 0, 0, 0, 0, 0, 0, 240, 207, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x0941, 0x0948}, // Devanagari Vowel Sign U ..Devanagari Vowel Sign Ai
|
||||||
247, 255, 253, 33, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 127, 0, 0, 240, 0, 248, 0, 0,
|
{0x094d, 0x094d}, // Devanagari Sign Virama ..Devanagari Sign Virama
|
||||||
0, 124, 0, 0, 0, 0, 0, 0, 31, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x0951, 0x0957}, // Devanagari Stress Sign U..Devanagari Vowel Sign Uu
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255,
|
{0x0962, 0x0963}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo
|
||||||
255, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128,
|
{0x0981, 0x0981}, // Bengali Sign Candrabindu..Bengali Sign Candrabindu
|
||||||
247, 63, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 68, 8, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0,
|
{0x09bc, 0x09bc}, // Bengali Sign Nukta ..Bengali Sign Nukta
|
||||||
255, 255, 3, 0, 0, 0, 0, 0, 192, 63, 0, 0, 128, 255, 3, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 200, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 102,
|
{0x09c1, 0x09c4}, // Bengali Vowel Sign U ..Bengali Vowel Sign Vocal
|
||||||
0, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 157, 193, 2, 0, 0, 0, 0, 48, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x09cd, 0x09cd}, // Bengali Sign Virama ..Bengali Sign Virama
|
||||||
0, 0, 0, 0, 0, 0, 32, 33, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0,
|
{0x09e2, 0x09e3}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal
|
||||||
127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x0a01, 0x0a02}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Bindi
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 110, 240, 0,
|
{0x0a3c, 0x0a3c}, // Gurmukhi Sign Nukta ..Gurmukhi Sign Nukta
|
||||||
0, 0, 0, 0, 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 255, 127, 0, 0, 0, 0, 0, 0, 0, 3, 0,
|
{0x0a41, 0x0a42}, // Gurmukhi Vowel Sign U ..Gurmukhi Vowel Sign Uu
|
||||||
0, 0, 0, 0, 120, 38, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 128, 239, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 0,
|
{0x0a47, 0x0a48}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x0a4b, 0x0a4d}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama
|
||||||
0, 0, 128, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 3, 248, 255, 231, 15, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0,
|
{0x0a51, 0x0a51}, // Gurmukhi Sign Udaat ..Gurmukhi Sign Udaat
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
|
{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
|
||||||
|
};
|
||||||
|
|
||||||
private static final short wtable[] = { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 18, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
// https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py
|
||||||
16, 16, 16, 16, 16, 16, 19, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20, 21, 22, 23, 24, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
|
// at commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02):
|
||||||
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
|
private static final int[][] WIDE_EASTASIAN = {
|
||||||
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
|
{0x1100, 0x115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler
|
||||||
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 26, 16, 16, 16, 16, 27, 16, 16, 17, 17, 17, 17, 17,
|
{0x231a, 0x231b}, // Watch ..Hourglass
|
||||||
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
|
{0x2329, 0x232a}, // Left-pointing Angle Brac..Right-pointing Angle Bra
|
||||||
17, 28, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17,
|
{0x23e9, 0x23ec}, // Black Right-pointing Dou..Black Down-pointing Doub
|
||||||
16, 16, 16, 29, 30, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x23f0, 0x23f0}, // Alarm Clock ..Alarm Clock
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x23f3, 0x23f3}, // Hourglass With Flowing S..Hourglass With Flowing S
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x25fd, 0x25fe}, // White Medium Small Squar..Black Medium Small Squar
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x2614, 0x2615}, // Umbrella With Rain Drops..Hot Beverage
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 31, 16, 16, 16,
|
{0x2648, 0x2653}, // Aries ..Pisces
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x267f, 0x267f}, // Wheelchair Symbol ..Wheelchair Symbol
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
{0x2693, 0x2693}, // Anch|| ..Anch||
|
||||||
16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255,
|
{0x26a1, 0x26a1}, // High Voltage Sign ..High Voltage Sign
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
{0x26aa, 0x26ab}, // Medium White Circle ..Medium Black Circle
|
||||||
255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x26bd, 0x26be}, // Soccer Ball ..Baseball
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 251, 255, 255, 255, 255, 255, 255,
|
{0x26c4, 0x26c5}, // Snowman Without Snow ..Sun Behind Cloud
|
||||||
255, 255, 255, 255, 15, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
{0x26ce, 0x26ce}, // Ophiuchus ..Ophiuchus
|
||||||
255, 255, 63, 0, 0, 0, 255, 15, 255, 255, 255, 255, 255, 255, 255, 127, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 254, 255, 255, 255,
|
{0x26d4, 0x26d4}, // No Entry ..No Entry
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 224, 255, 255, 255, 255, 63, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 255, 255,
|
{0x26ea, 0x26ea}, // Church ..Church
|
||||||
255, 255, 255, 7, 255, 255, 255, 255, 15, 0, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
{0x26f2, 0x26f3}, // Fountain ..Flag In Hole
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
{0x26f5, 0x26f5}, // Sailboat ..Sailboat
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
{0x26fa, 0x26fa}, // Tent ..Tent
|
||||||
255, 31, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x26fd, 0x26fd}, // Fuel Pump ..Fuel Pump
|
||||||
0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15, 0, 255, 255, 127, 248,
|
{0x2705, 0x2705}, // White Heavy Check Mark ..White Heavy Check Mark
|
||||||
255, 255, 255, 255, 255, 15, 0, 0, 255, 3, 0, 0, 255, 255, 255, 255, 247, 255, 127, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254,
|
{0x270a, 0x270b}, // Raised Fist ..Raised Hand
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x2728, 0x2728}, // Sparkles ..Sparkles
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 255, 255, 255, 255, 255, 7, 255, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
{0x274c, 0x274c}, // Cross Mark ..Cross Mark
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
{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) ..
|
||||||
|
};
|
||||||
|
|
||||||
/** Return the terminal display width of a code point: 0, 1 or 2. */
|
|
||||||
public static int width(int wc) {
|
private static boolean intable(int[][] table, int c) {
|
||||||
if (wc < 0xff) return (wc + 1 & 0x7f) >= 0x21 ? 1 : (wc != 0) ? 0 : 0;
|
// First quick check f|| Latin1 etc. characters.
|
||||||
if ((wc & 0xfffeffff) < 0xfffe) {
|
if (c < table[0][0]) return false;
|
||||||
if (((table[table[wc >> 8] * 32 + ((wc & 255) >> 3)] >> (wc & 7)) & 1) != 0) return 0;
|
|
||||||
if (((wtable[wtable[wc >> 8] * 32 + ((wc & 255) >> 3)] >> (wc & 7)) & 1) != 0) return 2;
|
// Binary search in table.
|
||||||
return 1;
|
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;
|
||||||
}
|
}
|
||||||
if ((wc & 0xfffe) == 0xfffe) return 0;
|
}
|
||||||
if (wc - 0x20000 < 0x20000) return 2;
|
return false;
|
||||||
if (wc == 0xe0001 || wc - 0xe0020 < 0x5f || wc - 0xe0100 < 0xef) return 0;
|
}
|
||||||
return 1;
|
|
||||||
|
/** 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;
|
||||||
|
|
||||||
|
// combining characters with zero width
|
||||||
|
if (intable(ZERO_WIDTH, ucs)) return 0;
|
||||||
|
|
||||||
|
return intable(WIDE_EASTASIAN, ucs) ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The width at an index position in a java char array. */
|
/** The width at an index position in a java char array. */
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import android.view.MotionEvent;
|
|||||||
import android.view.ScaleGestureDetector;
|
import android.view.ScaleGestureDetector;
|
||||||
|
|
||||||
/** A combination of {@link GestureDetector} and {@link ScaleGestureDetector}. */
|
/** A combination of {@link GestureDetector} and {@link ScaleGestureDetector}. */
|
||||||
public class GestureAndScaleRecognizer {
|
public final class GestureAndScaleRecognizer {
|
||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
boolean onSingleTapUp(MotionEvent e);
|
boolean onSingleTapUp(MotionEvent e);
|
||||||
@@ -29,6 +29,7 @@ public class GestureAndScaleRecognizer {
|
|||||||
private final GestureDetector mGestureDetector;
|
private final GestureDetector mGestureDetector;
|
||||||
private final ScaleGestureDetector mScaleDetector;
|
private final ScaleGestureDetector mScaleDetector;
|
||||||
final Listener mListener;
|
final Listener mListener;
|
||||||
|
boolean isAfterLongPress;
|
||||||
|
|
||||||
public GestureAndScaleRecognizer(Context context, Listener listener) {
|
public GestureAndScaleRecognizer(Context context, Listener listener) {
|
||||||
mListener = listener;
|
mListener = listener;
|
||||||
@@ -52,6 +53,7 @@ public class GestureAndScaleRecognizer {
|
|||||||
@Override
|
@Override
|
||||||
public void onLongPress(MotionEvent e) {
|
public void onLongPress(MotionEvent e) {
|
||||||
mListener.onLongPress(e);
|
mListener.onLongPress(e);
|
||||||
|
isAfterLongPress = true;
|
||||||
}
|
}
|
||||||
}, null, true /* ignoreMultitouch */);
|
}, null, true /* ignoreMultitouch */);
|
||||||
|
|
||||||
@@ -88,9 +90,18 @@ public class GestureAndScaleRecognizer {
|
|||||||
public void onTouchEvent(MotionEvent event) {
|
public void onTouchEvent(MotionEvent event) {
|
||||||
mGestureDetector.onTouchEvent(event);
|
mGestureDetector.onTouchEvent(event);
|
||||||
mScaleDetector.onTouchEvent(event);
|
mScaleDetector.onTouchEvent(event);
|
||||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
isAfterLongPress = false;
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
if (!isAfterLongPress) {
|
||||||
|
// This behaviour is desired when in e.g. vim with mouse events, where we do not
|
||||||
|
// want to move the cursor when lifting finger after a long press.
|
||||||
mListener.onUp(event);
|
mListener.onUp(event);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInProgress() {
|
public boolean isInProgress() {
|
||||||
|
|||||||
@@ -1,20 +1,37 @@
|
|||||||
package com.termux.view;
|
package com.termux.view;
|
||||||
|
|
||||||
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.ScaleGestureDetector;
|
import android.view.ScaleGestureDetector;
|
||||||
|
|
||||||
|
import com.termux.terminal.TerminalSession;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Input and scale listener which may be set on a {@link TerminalView} through
|
* Input and scale listener which may be set on a {@link TerminalView} through
|
||||||
* {@link TerminalView#setOnKeyListener(TerminalKeyListener)}.
|
* {@link TerminalView#setOnKeyListener(TerminalKeyListener)}.
|
||||||
|
* <p/>
|
||||||
|
* TODO: Rename to TerminalViewClient.
|
||||||
*/
|
*/
|
||||||
public interface TerminalKeyListener {
|
public interface TerminalKeyListener {
|
||||||
|
|
||||||
/** Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}. */
|
/** Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}. */
|
||||||
float onScale(float scale);
|
float onScale(float scale);
|
||||||
|
|
||||||
void onLongPress(MotionEvent e);
|
|
||||||
|
|
||||||
/** On a single tap on the terminal if terminal mouse reporting not enabled. */
|
/** On a single tap on the terminal if terminal mouse reporting not enabled. */
|
||||||
void onSingleTapUp(MotionEvent e);
|
void onSingleTapUp(MotionEvent e);
|
||||||
|
|
||||||
|
boolean shouldBackButtonBeMappedToEscape();
|
||||||
|
|
||||||
|
void copyModeChanged(boolean copyMode);
|
||||||
|
|
||||||
|
boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session);
|
||||||
|
|
||||||
|
boolean onKeyUp(int keyCode, KeyEvent e);
|
||||||
|
|
||||||
|
boolean readControlKey();
|
||||||
|
|
||||||
|
boolean readAltKey();
|
||||||
|
|
||||||
|
boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import com.termux.terminal.WcWidth;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Renderer of a {@link TerminalEmulator} into a {@link Canvas}.
|
* Renderer of a {@link TerminalEmulator} into a {@link Canvas}.
|
||||||
*
|
* <p/>
|
||||||
* Saves font metrics, so needs to be recreated each time the typeface or font size changes.
|
* Saves font metrics, so needs to be recreated each time the typeface or font size changes.
|
||||||
*/
|
*/
|
||||||
final class TerminalRenderer {
|
final class TerminalRenderer {
|
||||||
@@ -64,8 +64,8 @@ final class TerminalRenderer {
|
|||||||
final TerminalBuffer screen = mEmulator.getScreen();
|
final TerminalBuffer screen = mEmulator.getScreen();
|
||||||
final int[] palette = mEmulator.mColors.mCurrentColors;
|
final int[] palette = mEmulator.mColors.mCurrentColors;
|
||||||
|
|
||||||
int fillColor = palette[reverseVideo ? TextStyle.COLOR_INDEX_FOREGROUND : TextStyle.COLOR_INDEX_BACKGROUND];
|
if (reverseVideo)
|
||||||
canvas.drawColor(fillColor, PorterDuff.Mode.SRC);
|
canvas.drawColor(palette[TextStyle.COLOR_INDEX_FOREGROUND], PorterDuff.Mode.SRC);
|
||||||
|
|
||||||
float heightOffset = mFontLineSpacingAndAscent;
|
float heightOffset = mFontLineSpacingAndAscent;
|
||||||
for (int row = topRow; row < endRow; row++) {
|
for (int row = topRow; row < endRow; row++) {
|
||||||
@@ -80,8 +80,9 @@ final class TerminalRenderer {
|
|||||||
|
|
||||||
TerminalRow lineObject = screen.allocateFullLineIfNecessary(screen.externalToInternalRow(row));
|
TerminalRow lineObject = screen.allocateFullLineIfNecessary(screen.externalToInternalRow(row));
|
||||||
final char[] line = lineObject.mText;
|
final char[] line = lineObject.mText;
|
||||||
|
final int charsUsedInLine = lineObject.getSpaceUsed();
|
||||||
|
|
||||||
int lastRunStyle = 0;
|
long lastRunStyle = 0;
|
||||||
boolean lastRunInsideCursor = false;
|
boolean lastRunInsideCursor = false;
|
||||||
int lastRunStartColumn = -1;
|
int lastRunStartColumn = -1;
|
||||||
int lastRunStartIndex = 0;
|
int lastRunStartIndex = 0;
|
||||||
@@ -89,14 +90,14 @@ final class TerminalRenderer {
|
|||||||
int currentCharIndex = 0;
|
int currentCharIndex = 0;
|
||||||
float measuredWidthForRun = 0.f;
|
float measuredWidthForRun = 0.f;
|
||||||
|
|
||||||
for (int column = 0; column < columns;) {
|
for (int column = 0; column < columns; ) {
|
||||||
final char charAtIndex = line[currentCharIndex];
|
final char charAtIndex = line[currentCharIndex];
|
||||||
final boolean charIsHighsurrogate = Character.isHighSurrogate(charAtIndex);
|
final boolean charIsHighsurrogate = Character.isHighSurrogate(charAtIndex);
|
||||||
final int charsForCodePoint = charIsHighsurrogate ? 2 : 1;
|
final int charsForCodePoint = charIsHighsurrogate ? 2 : 1;
|
||||||
final int codePoint = charIsHighsurrogate ? Character.toCodePoint(charAtIndex, line[currentCharIndex + 1]) : charAtIndex;
|
final int codePoint = charIsHighsurrogate ? Character.toCodePoint(charAtIndex, line[currentCharIndex + 1]) : charAtIndex;
|
||||||
final int codePointWcWidth = WcWidth.width(codePoint);
|
final int codePointWcWidth = WcWidth.width(codePoint);
|
||||||
final boolean insideCursor = (column >= selx1 && column <= selx2) || (cursorX == column || (codePointWcWidth == 2 && cursorX == column + 1));
|
final boolean insideCursor = (column >= selx1 && column <= selx2) || (cursorX == column || (codePointWcWidth == 2 && cursorX == column + 1));
|
||||||
final int style = lineObject.getStyle(column);
|
final long style = lineObject.getStyle(column);
|
||||||
|
|
||||||
// Check if the measured text width for this code point is not the same as that expected by wcwidth().
|
// Check if the measured text width for this code point is not the same as that expected by wcwidth().
|
||||||
// This could happen for some fonts which are not truly monospace, or for more exotic characters such as
|
// This could happen for some fonts which are not truly monospace, or for more exotic characters such as
|
||||||
@@ -125,7 +126,7 @@ final class TerminalRenderer {
|
|||||||
measuredWidthForRun += measuredCodePointWidth;
|
measuredWidthForRun += measuredCodePointWidth;
|
||||||
column += codePointWcWidth;
|
column += codePointWcWidth;
|
||||||
currentCharIndex += charsForCodePoint;
|
currentCharIndex += charsForCodePoint;
|
||||||
while (WcWidth.width(line, currentCharIndex) <= 0) {
|
while (currentCharIndex < charsUsedInLine && WcWidth.width(line, currentCharIndex) <= 0) {
|
||||||
// Eat combining chars so that they are treated as part of the last non-combining code point,
|
// Eat combining chars so that they are treated as part of the last non-combining code point,
|
||||||
// instead of e.g. being considered inside the cursor in the next run.
|
// instead of e.g. being considered inside the cursor in the next run.
|
||||||
currentCharIndex += Character.isHighSurrogate(line[currentCharIndex]) ? 2 : 1;
|
currentCharIndex += Character.isHighSurrogate(line[currentCharIndex]) ? 2 : 1;
|
||||||
@@ -140,33 +141,30 @@ final class TerminalRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param canvas
|
* @param canvas the canvas to render on
|
||||||
* the canvas to render on
|
* @param palette the color palette to look up colors from textStyle
|
||||||
* @param palette
|
* @param y height offset into the canvas where to render the line: line * {@link #mFontLineSpacing}
|
||||||
* the color palette to look up colors from textStyle
|
* @param startColumn the run offset in columns
|
||||||
* @param y
|
* @param runWidthColumns the run width in columns - this is computed from wcwidth() and may not be what the font measures to
|
||||||
* height offset into the canvas where to render the line: line * {@link #mFontLineSpacing}
|
* @param text the java char array to render text from
|
||||||
* @param startColumn
|
* @param startCharIndex index into the text array where to start
|
||||||
* the run offset in columns
|
* @param runWidthChars number of java characters from the text array to render
|
||||||
* @param runWidthColumns
|
* @param cursor true if rendering a cursor or selection
|
||||||
* the run width in columns - this is computed from wcwidth() and may not be what the font measures to
|
* @param textStyle the background, foreground and effect encoded using {@link TextStyle}
|
||||||
* @param text
|
* @param reverseVideo if the screen is rendered with the global reverse video flag set
|
||||||
* the java char array to render text from
|
|
||||||
* @param startCharIndex
|
|
||||||
* index into the text array where to start
|
|
||||||
* @param runWidthChars
|
|
||||||
* number of java characters from the text array to render
|
|
||||||
* @param cursor
|
|
||||||
* true if rendering a cursor or selection
|
|
||||||
* @param textStyle
|
|
||||||
* the background, foreground and effect encoded using {@link TextStyle}
|
|
||||||
* @param reverseVideo
|
|
||||||
* if the screen is rendered with the global reverse video flag set
|
|
||||||
*/
|
*/
|
||||||
private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int startColumn, int runWidthColumns, int startCharIndex, int runWidthChars,
|
private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int startColumn, int runWidthColumns, int startCharIndex, int runWidthChars,
|
||||||
float mes, boolean cursor, int textStyle, boolean reverseVideo) {
|
float mes, boolean cursor, long textStyle, boolean reverseVideo) {
|
||||||
int foreColor = TextStyle.decodeForeColor(textStyle);
|
int foreColor = TextStyle.decodeForeColor(textStyle);
|
||||||
int backColor = TextStyle.decodeBackColor(textStyle);
|
int backColor = TextStyle.decodeBackColor(textStyle);
|
||||||
|
|
||||||
|
int foreColorIndex = -1;
|
||||||
|
if ((foreColor & 0xff000000) != 0xff000000) {
|
||||||
|
foreColorIndex = foreColor;
|
||||||
|
foreColor = palette[foreColor];
|
||||||
|
}
|
||||||
|
if ((backColor & 0xff000000) != 0xff000000) backColor = palette[backColor];
|
||||||
|
|
||||||
final int effect = TextStyle.decodeEffect(textStyle);
|
final int effect = TextStyle.decodeEffect(textStyle);
|
||||||
float left = startColumn * mFontWidth;
|
float left = startColumn * mFontWidth;
|
||||||
float right = left + runWidthColumns * mFontWidth;
|
float right = left + runWidthColumns * mFontWidth;
|
||||||
@@ -190,9 +188,9 @@ final class TerminalRenderer {
|
|||||||
backColor = tmp;
|
backColor = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backColor != TextStyle.COLOR_INDEX_BACKGROUND) {
|
if (backColor != palette[TextStyle.COLOR_INDEX_BACKGROUND]) {
|
||||||
// Only draw non-default background.
|
// Only draw non-default background.
|
||||||
mTextPaint.setColor(palette[backColor]);
|
mTextPaint.setColor(backColor);
|
||||||
canvas.drawRect(left, y - mFontLineSpacingAndAscent + mFontAscent, right, y, mTextPaint);
|
canvas.drawRect(left, y - mFontLineSpacingAndAscent + mFontAscent, right, y, mTextPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,24 +202,26 @@ final class TerminalRenderer {
|
|||||||
final boolean strikeThrough = (effect & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0;
|
final boolean strikeThrough = (effect & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0;
|
||||||
final boolean dim = (effect & TextStyle.CHARACTER_ATTRIBUTE_DIM) != 0;
|
final boolean dim = (effect & TextStyle.CHARACTER_ATTRIBUTE_DIM) != 0;
|
||||||
|
|
||||||
int foreColorARGB = palette[foreColor];
|
// Let bold have bright colors if applicable (one of the first 8):
|
||||||
|
if (bold && foreColorIndex >= 0 && foreColorIndex < 8) foreColor = palette[foreColorIndex + 8];
|
||||||
|
|
||||||
if (dim) {
|
if (dim) {
|
||||||
int red = (0xFF & (foreColorARGB >> 16));
|
int red = (0xFF & (foreColor >> 16));
|
||||||
int green = (0xFF & (foreColorARGB >> 8));
|
int green = (0xFF & (foreColor >> 8));
|
||||||
int blue = (0xFF & foreColorARGB);
|
int blue = (0xFF & foreColor);
|
||||||
// Dim color handling used by libvte which in turn took it from xterm
|
// Dim color handling used by libvte which in turn took it from xterm
|
||||||
// (https://bug735245.bugzilla-attachments.gnome.org/attachment.cgi?id=284267):
|
// (https://bug735245.bugzilla-attachments.gnome.org/attachment.cgi?id=284267):
|
||||||
red = red * 2 / 3;
|
red = red * 2 / 3;
|
||||||
green = green * 2 / 3;
|
green = green * 2 / 3;
|
||||||
blue = blue * 2 / 3;
|
blue = blue * 2 / 3;
|
||||||
foreColorARGB = 0xFF000000 + (red << 16) + (green << 8) + blue;
|
foreColor = 0xFF000000 + (red << 16) + (green << 8) + blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
mTextPaint.setFakeBoldText(bold);
|
mTextPaint.setFakeBoldText(bold);
|
||||||
mTextPaint.setUnderlineText(underline);
|
mTextPaint.setUnderlineText(underline);
|
||||||
mTextPaint.setTextSkewX(italic ? -0.35f : 0.f);
|
mTextPaint.setTextSkewX(italic ? -0.35f : 0.f);
|
||||||
mTextPaint.setStrikeThruText(strikeThrough);
|
mTextPaint.setStrikeThruText(strikeThrough);
|
||||||
mTextPaint.setColor(foreColorARGB);
|
mTextPaint.setColor(foreColor);
|
||||||
|
|
||||||
// The text alignment is the default Paint.Align.LEFT.
|
// The text alignment is the default Paint.Align.LEFT.
|
||||||
canvas.drawText(text, startCharIndex, runWidthChars, left, y - mFontLineSpacingAndAscent, mTextPaint);
|
canvas.drawText(text, startCharIndex, runWidthChars, left, y - mFontLineSpacingAndAscent, mTextPaint);
|
||||||
|
|||||||
@@ -1,29 +1,27 @@
|
|||||||
package com.termux.view;
|
package com.termux.view;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import com.termux.terminal.EmulatorDebug;
|
|
||||||
import com.termux.terminal.KeyHandler;
|
|
||||||
import com.termux.terminal.TerminalColors;
|
|
||||||
import com.termux.terminal.TerminalEmulator;
|
|
||||||
import com.termux.terminal.TerminalSession;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.text.Editable;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.ActionMode;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.KeyCharacterMap;
|
import android.view.KeyCharacterMap;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.BaseInputConnection;
|
import android.view.inputmethod.BaseInputConnection;
|
||||||
@@ -31,6 +29,13 @@ import android.view.inputmethod.EditorInfo;
|
|||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
import android.widget.Scroller;
|
import android.widget.Scroller;
|
||||||
|
|
||||||
|
import com.termux.R;
|
||||||
|
import com.termux.terminal.EmulatorDebug;
|
||||||
|
import com.termux.terminal.KeyHandler;
|
||||||
|
import com.termux.terminal.TerminalBuffer;
|
||||||
|
import com.termux.terminal.TerminalEmulator;
|
||||||
|
import com.termux.terminal.TerminalSession;
|
||||||
|
|
||||||
/** View displaying and interacting with a {@link TerminalSession}. */
|
/** View displaying and interacting with a {@link TerminalSession}. */
|
||||||
public final class TerminalView extends View {
|
public final class TerminalView extends View {
|
||||||
|
|
||||||
@@ -49,12 +54,11 @@ public final class TerminalView extends View {
|
|||||||
/** The top row of text to display. Ranges from -activeTranscriptRows to 0. */
|
/** The top row of text to display. Ranges from -activeTranscriptRows to 0. */
|
||||||
int mTopRow;
|
int mTopRow;
|
||||||
|
|
||||||
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
|
boolean mIsSelectingText = false, mIsDraggingLeftSelection, mInitialTextSelection;
|
||||||
boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
|
|
||||||
|
|
||||||
boolean mIsSelectingText = false;
|
|
||||||
int mSelXAnchor = -1, mSelYAnchor = -1;
|
|
||||||
int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1;
|
int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1;
|
||||||
|
float mSelectionDownX, mSelectionDownY;
|
||||||
|
private ActionMode mActionMode;
|
||||||
|
private BitmapDrawable mLeftSelectionHandle, mRightSelectionHandle;
|
||||||
|
|
||||||
float mScaleFactor = 1.f;
|
float mScaleFactor = 1.f;
|
||||||
final GestureAndScaleRecognizer mGestureRecognizer;
|
final GestureAndScaleRecognizer mGestureRecognizer;
|
||||||
@@ -76,22 +80,29 @@ public final class TerminalView extends View {
|
|||||||
super(context, attributes);
|
super(context, attributes);
|
||||||
mGestureRecognizer = new GestureAndScaleRecognizer(context, new GestureAndScaleRecognizer.Listener() {
|
mGestureRecognizer = new GestureAndScaleRecognizer(context, new GestureAndScaleRecognizer.Listener() {
|
||||||
|
|
||||||
|
boolean scrolledWithFinger;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onUp(MotionEvent e) {
|
public boolean onUp(MotionEvent e) {
|
||||||
mScrollRemainder = 0.0f;
|
mScrollRemainder = 0.0f;
|
||||||
if (mEmulator != null && mEmulator.isMouseTrackingActive()) {
|
if (mEmulator != null && mEmulator.isMouseTrackingActive() && !mIsSelectingText && !scrolledWithFinger) {
|
||||||
// Quick event processing when mouse tracking is active - do not wait for check of double tapping
|
// Quick event processing when mouse tracking is active - do not wait for check of double tapping
|
||||||
// for zooming.
|
// for zooming.
|
||||||
sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, true);
|
sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, true);
|
||||||
sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, false);
|
sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
scrolledWithFinger = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onSingleTapUp(MotionEvent e) {
|
public boolean onSingleTapUp(MotionEvent e) {
|
||||||
if (mEmulator == null) return true;
|
if (mEmulator == null) return true;
|
||||||
|
if (mIsSelectingText) {
|
||||||
|
toggleSelectingText(null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
requestFocus();
|
requestFocus();
|
||||||
if (!mEmulator.isMouseTrackingActive()) {
|
if (!mEmulator.isMouseTrackingActive()) {
|
||||||
if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) {
|
if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) {
|
||||||
@@ -103,25 +114,27 @@ public final class TerminalView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onScroll(MotionEvent e2, float distanceX, float distanceY) {
|
public boolean onScroll(MotionEvent e, float distanceX, float distanceY) {
|
||||||
if (mEmulator == null) return true;
|
if (mEmulator == null || mIsSelectingText) return true;
|
||||||
if (mEmulator.isMouseTrackingActive() && e2.isFromSource(InputDevice.SOURCE_MOUSE)) {
|
if (mEmulator.isMouseTrackingActive() && e.isFromSource(InputDevice.SOURCE_MOUSE)) {
|
||||||
// If moving with mouse pointer while pressing button, report that instead of scroll.
|
// If moving with mouse pointer while pressing button, report that instead of scroll.
|
||||||
// This means that we never report moving with button press-events for touch input,
|
// This means that we never report moving with button press-events for touch input,
|
||||||
// since we cannot just start sending these events without a starting press event,
|
// since we cannot just start sending these events without a starting press event,
|
||||||
// which we do not do for touch input, only mouse in onTouchEvent().
|
// which we do not do for touch input, only mouse in onTouchEvent().
|
||||||
sendMouseEventCode(e2, TerminalEmulator.MOUSE_LEFT_BUTTON_MOVED, true);
|
sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON_MOVED, true);
|
||||||
} else {
|
} else {
|
||||||
|
scrolledWithFinger = true;
|
||||||
distanceY += mScrollRemainder;
|
distanceY += mScrollRemainder;
|
||||||
int deltaRows = (int) (distanceY / mRenderer.mFontLineSpacing);
|
int deltaRows = (int) (distanceY / mRenderer.mFontLineSpacing);
|
||||||
mScrollRemainder = distanceY - deltaRows * mRenderer.mFontLineSpacing;
|
mScrollRemainder = distanceY - deltaRows * mRenderer.mFontLineSpacing;
|
||||||
doScroll(e2, deltaRows);
|
doScroll(e, deltaRows);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onScale(float focusX, float focusY, float scale) {
|
public boolean onScale(float focusX, float focusY, float scale) {
|
||||||
|
if (mEmulator == null || mIsSelectingText) return true;
|
||||||
mScaleFactor *= scale;
|
mScaleFactor *= scale;
|
||||||
mScaleFactor = mOnKeyListener.onScale(mScaleFactor);
|
mScaleFactor = mOnKeyListener.onScale(mScaleFactor);
|
||||||
return true;
|
return true;
|
||||||
@@ -129,7 +142,7 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onFling(final MotionEvent e2, float velocityX, float velocityY) {
|
public boolean onFling(final MotionEvent e2, float velocityX, float velocityY) {
|
||||||
if (mEmulator == null) return true;
|
if (mEmulator == null || mIsSelectingText) return true;
|
||||||
// Do not start scrolling until last fling has been taken care of:
|
// Do not start scrolling until last fling has been taken care of:
|
||||||
if (!mScroller.isFinished()) return true;
|
if (!mScroller.isFinished()) return true;
|
||||||
|
|
||||||
@@ -176,9 +189,9 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLongPress(MotionEvent e) {
|
public void onLongPress(MotionEvent e) {
|
||||||
if (mEmulator != null && !mGestureRecognizer.isInProgress()) {
|
if (!mGestureRecognizer.isInProgress() && !mIsSelectingText) {
|
||||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
mOnKeyListener.onLongPress(e);
|
toggleSelectingText(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -186,8 +199,7 @@ public final class TerminalView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param onKeyListener
|
* @param onKeyListener Listener for all kinds of key events, both hardware and IME (which makes it different from that
|
||||||
* Listener for all kinds of key events, both hardware and IME (which makes it different from that
|
|
||||||
* available with {@link View#setOnKeyListener(OnKeyListener)}.
|
* available with {@link View#setOnKeyListener(OnKeyListener)}.
|
||||||
*/
|
*/
|
||||||
public void setOnKeyListener(TerminalKeyListener onKeyListener) {
|
public void setOnKeyListener(TerminalKeyListener onKeyListener) {
|
||||||
@@ -197,8 +209,7 @@ public final class TerminalView extends View {
|
|||||||
/**
|
/**
|
||||||
* Attach a {@link TerminalSession} to this view.
|
* Attach a {@link TerminalSession} to this view.
|
||||||
*
|
*
|
||||||
* @param session
|
* @param session The {@link TerminalSession} this view will be displaying.
|
||||||
* The {@link TerminalSession} this view will be displaying.
|
|
||||||
*/
|
*/
|
||||||
public boolean attachSession(TerminalSession session) {
|
public boolean attachSession(TerminalSession session) {
|
||||||
if (session == mTermSession) return false;
|
if (session == mTermSession) return false;
|
||||||
@@ -218,72 +229,58 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
||||||
// Make the IME run in a limited "generate key events" mode.
|
// Using InputType.NULL is the most correct input type and avoids issues with other hacks.
|
||||||
//
|
//
|
||||||
// If using just "TYPE_NULL", there is a problem with the "Google Pinyin Input" being in
|
// Previous keyboard issues:
|
||||||
// word mode when used with the "En" tab available when the "Show English keyboard" option
|
// https://github.com/termux/termux-packages/issues/25
|
||||||
// is enabled - see https://github.com/termux/termux-packages/issues/25.
|
// https://github.com/termux/termux-app/issues/87.
|
||||||
//
|
// https://github.com/termux/termux-app/issues/126.
|
||||||
// Adding TYPE_TEXT_FLAG_NO_SUGGESTIONS fixes Pinyin Input, put causes Swype to be put in
|
// https://github.com/termux/termux-app/issues/137 (japanese chars and TYPE_NULL).
|
||||||
// word mode... Using TYPE_TEXT_VARIATION_VISIBLE_PASSWORD fixes that.
|
outAttrs.inputType = InputType.TYPE_NULL;
|
||||||
//
|
|
||||||
// So a bit messy. If this gets too messy it's perhaps best resolved by reverting back to just
|
|
||||||
// "TYPE_NULL" and let the Pinyin Input english keyboard be in word mode.
|
|
||||||
outAttrs.inputType = InputType.TYPE_NULL | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
|
|
||||||
|
|
||||||
// Let part of the application show behind when in landscape:
|
// Let part of the application show behind when in landscape:
|
||||||
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN;
|
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN;
|
||||||
|
|
||||||
return new BaseInputConnection(this, true) {
|
return new BaseInputConnection(this, true) {
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean beginBatchEdit() {
|
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: beginBatchEdit()");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean clearMetaKeyStates(int states) {
|
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: clearMetaKeyStates(" + states + ")");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean endBatchEdit() {
|
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: endBatchEdit()");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean finishComposingText() {
|
public boolean finishComposingText() {
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: finishComposingText()");
|
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: finishComposingText()");
|
||||||
|
super.finishComposingText();
|
||||||
|
|
||||||
|
sendTextToTerminal(getEditable());
|
||||||
|
getEditable().clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCursorCapsMode(int reqModes) {
|
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: getCursorCapsMode(" + reqModes + ")");
|
|
||||||
int mode = 0;
|
|
||||||
if ((reqModes & TextUtils.CAP_MODE_CHARACTERS) != 0) {
|
|
||||||
mode |= TextUtils.CAP_MODE_CHARACTERS;
|
|
||||||
}
|
|
||||||
return mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharSequence getTextAfterCursor(int n, int flags) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharSequence getTextBeforeCursor(int n, int flags) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean commitText(CharSequence text, int newCursorPosition) {
|
public boolean commitText(CharSequence text, int newCursorPosition) {
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")");
|
if (LOG_KEY_EVENTS) {
|
||||||
|
Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")");
|
||||||
|
}
|
||||||
|
super.commitText(text, newCursorPosition);
|
||||||
|
|
||||||
if (mEmulator == null) return true;
|
if (mEmulator == null) return true;
|
||||||
|
|
||||||
|
Editable content = getEditable();
|
||||||
|
sendTextToTerminal(content);
|
||||||
|
content.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean deleteSurroundingText(int leftLength, int rightLength) {
|
||||||
|
if (LOG_KEY_EVENTS)
|
||||||
|
Log.i(EmulatorDebug.LOG_TAG, "IME: deleteSurroundingText(" + leftLength + ", " + rightLength + ")");
|
||||||
|
// If leftLength=2 it may be due to a UTF-16 surrogate pair. So we cannot send
|
||||||
|
// multiple key events for that. Let's just hope that keyboards don't use
|
||||||
|
// leftLength > 1 for other purposes (such as holding down backspace for repeat).
|
||||||
|
sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
|
||||||
|
return super.deleteSurroundingText(leftLength, rightLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void sendTextToTerminal(CharSequence text) {
|
||||||
final int textLengthInChars = text.length();
|
final int textLengthInChars = text.length();
|
||||||
for (int i = 0; i < textLengthInChars; i++) {
|
for (int i = 0; i < textLengthInChars; i++) {
|
||||||
char firstChar = text.charAt(i);
|
char firstChar = text.charAt(i);
|
||||||
@@ -298,21 +295,32 @@ public final class TerminalView extends View {
|
|||||||
} else {
|
} else {
|
||||||
codePoint = firstChar;
|
codePoint = firstChar;
|
||||||
}
|
}
|
||||||
inputCodePoint(codePoint, false, false);
|
|
||||||
|
boolean ctrlHeld = false;
|
||||||
|
if (codePoint <= 31 && codePoint != 27) {
|
||||||
|
// E.g. penti keyboard for ctrl input.
|
||||||
|
ctrlHeld = true;
|
||||||
|
switch (codePoint) {
|
||||||
|
case 31:
|
||||||
|
codePoint = '_';
|
||||||
|
break;
|
||||||
|
case 30:
|
||||||
|
codePoint = '^';
|
||||||
|
break;
|
||||||
|
case 29:
|
||||||
|
codePoint = ']';
|
||||||
|
break;
|
||||||
|
case 28:
|
||||||
|
codePoint = '\\';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
codePoint += 96;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
inputCodePoint(codePoint, ctrlHeld, false);
|
||||||
public boolean deleteSurroundingText(int leftLength, int rightLength) {
|
}
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: deleteSurroundingText(" + leftLength + ", " + rightLength + ")");
|
|
||||||
|
|
||||||
// Swype keyboard sometimes(?) sends this on backspace:
|
|
||||||
if (leftLength == 0 && rightLength == 0) leftLength = 1;
|
|
||||||
|
|
||||||
for (int i = 0; i < leftLength; i++)
|
|
||||||
sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -336,19 +344,35 @@ public final class TerminalView extends View {
|
|||||||
public void onScreenUpdated() {
|
public void onScreenUpdated() {
|
||||||
if (mEmulator == null) return;
|
if (mEmulator == null) return;
|
||||||
|
|
||||||
|
boolean skipScrolling = false;
|
||||||
if (mIsSelectingText) {
|
if (mIsSelectingText) {
|
||||||
|
// Do not scroll when selecting text.
|
||||||
|
int rowsInHistory = mEmulator.getScreen().getActiveTranscriptRows();
|
||||||
int rowShift = mEmulator.getScrollCounter();
|
int rowShift = mEmulator.getScrollCounter();
|
||||||
|
if (-mTopRow + rowShift > rowsInHistory) {
|
||||||
|
// .. unless we're hitting the end of history transcript, in which
|
||||||
|
// case we abort text selection and scroll to end.
|
||||||
|
toggleSelectingText(null);
|
||||||
|
} else {
|
||||||
|
skipScrolling = true;
|
||||||
|
mTopRow -= rowShift;
|
||||||
mSelY1 -= rowShift;
|
mSelY1 -= rowShift;
|
||||||
mSelY2 -= rowShift;
|
mSelY2 -= rowShift;
|
||||||
mSelYAnchor -= rowShift;
|
|
||||||
}
|
}
|
||||||
mEmulator.clearScrollCounter();
|
}
|
||||||
|
|
||||||
if (mTopRow != 0) {
|
if (!skipScrolling && mTopRow != 0) {
|
||||||
// Scroll down if not already there.
|
// Scroll down if not already there.
|
||||||
mTopRow = 0;
|
if (mTopRow < -3) {
|
||||||
scrollTo(0, 0);
|
// Awaken scroll bars only if scrolling a noticeable amount
|
||||||
|
// - we do not want visible scroll bars during normal typing
|
||||||
|
// of one row at a time.
|
||||||
|
awakenScrollBars();
|
||||||
}
|
}
|
||||||
|
mTopRow = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mEmulator.clearScrollCounter();
|
||||||
|
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
@@ -356,14 +380,19 @@ public final class TerminalView extends View {
|
|||||||
/**
|
/**
|
||||||
* Sets the text size, which in turn sets the number of rows and columns.
|
* Sets the text size, which in turn sets the number of rows and columns.
|
||||||
*
|
*
|
||||||
* @param textSize
|
* @param textSize the new font size, in density-independent pixels.
|
||||||
* the new font size, in density-independent pixels.
|
|
||||||
*/
|
*/
|
||||||
public void setTextSize(int textSize) {
|
public void setTextSize(int textSize) {
|
||||||
mRenderer = new TerminalRenderer(textSize, mRenderer == null ? Typeface.MONOSPACE : mRenderer.mTypeface);
|
mRenderer = new TerminalRenderer(textSize, mRenderer == null ? Typeface.MONOSPACE : mRenderer.mTypeface);
|
||||||
updateSize();
|
updateSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTypeface(Typeface newTypeface) {
|
||||||
|
mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface);
|
||||||
|
updateSize();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCheckIsTextEditor() {
|
public boolean onCheckIsTextEditor() {
|
||||||
return true;
|
return true;
|
||||||
@@ -423,78 +452,103 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@Override
|
@Override
|
||||||
|
@TargetApi(23)
|
||||||
public boolean onTouchEvent(MotionEvent ev) {
|
public boolean onTouchEvent(MotionEvent ev) {
|
||||||
if (mEmulator == null) return true;
|
if (mEmulator == null) return true;
|
||||||
final boolean eventFromMouse = ev.isFromSource(InputDevice.SOURCE_MOUSE);
|
|
||||||
final int action = ev.getAction();
|
final int action = ev.getAction();
|
||||||
|
|
||||||
if (eventFromMouse) {
|
if (mIsSelectingText) {
|
||||||
if ((ev.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
|
int cy = (int) (ev.getY() / mRenderer.mFontLineSpacing) + mTopRow;
|
||||||
|
int cx = (int) (ev.getX() / mRenderer.mFontWidth);
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
mInitialTextSelection = false;
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
int distanceFromSel1 = Math.abs(cx - mSelX1) + Math.abs(cy - mSelY1);
|
||||||
|
int distanceFromSel2 = Math.abs(cx - mSelX2) + Math.abs(cy - mSelY2);
|
||||||
|
mIsDraggingLeftSelection = distanceFromSel1 <= distanceFromSel2;
|
||||||
|
mSelectionDownX = ev.getX();
|
||||||
|
mSelectionDownY = ev.getY();
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
if (mInitialTextSelection) break;
|
||||||
|
float deltaX = ev.getX() - mSelectionDownX;
|
||||||
|
float deltaY = ev.getY() - mSelectionDownY;
|
||||||
|
int deltaCols = (int) Math.ceil(deltaX / mRenderer.mFontWidth);
|
||||||
|
int deltaRows = (int) Math.ceil(deltaY / mRenderer.mFontLineSpacing);
|
||||||
|
mSelectionDownX += deltaCols * mRenderer.mFontWidth;
|
||||||
|
mSelectionDownY += deltaRows * mRenderer.mFontLineSpacing;
|
||||||
|
if (mIsDraggingLeftSelection) {
|
||||||
|
mSelX1 += deltaCols;
|
||||||
|
mSelY1 += deltaRows;
|
||||||
|
} else {
|
||||||
|
mSelX2 += deltaCols;
|
||||||
|
mSelY2 += deltaRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
mSelX1 = Math.min(mEmulator.mColumns, Math.max(0, mSelX1));
|
||||||
|
mSelX2 = Math.min(mEmulator.mColumns, Math.max(0, mSelX2));
|
||||||
|
|
||||||
|
if (mSelY1 == mSelY2 && mSelX1 > mSelX2 || mSelY1 > mSelY2) {
|
||||||
|
// Switch handles.
|
||||||
|
mIsDraggingLeftSelection = !mIsDraggingLeftSelection;
|
||||||
|
int tmpX1 = mSelX1, tmpY1 = mSelY1;
|
||||||
|
mSelX1 = mSelX2;
|
||||||
|
mSelY1 = mSelY2;
|
||||||
|
mSelX2 = tmpX1;
|
||||||
|
mSelY2 = tmpY1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||||
|
mActionMode.invalidateContentRect();
|
||||||
|
invalidate();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mGestureRecognizer.onTouchEvent(ev);
|
||||||
|
return true;
|
||||||
|
} else if (ev.isFromSource(InputDevice.SOURCE_MOUSE)) {
|
||||||
|
if (ev.isButtonPressed(MotionEvent.BUTTON_SECONDARY)) {
|
||||||
if (action == MotionEvent.ACTION_DOWN) showContextMenu();
|
if (action == MotionEvent.ACTION_DOWN) showContextMenu();
|
||||||
return true;
|
return true;
|
||||||
} else if (mEmulator.isMouseTrackingActive() && (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_UP)) {
|
} else if (ev.isButtonPressed(MotionEvent.BUTTON_TERTIARY)) {
|
||||||
sendMouseEventCode(ev, TerminalEmulator.MOUSE_LEFT_BUTTON, ev.getAction() == MotionEvent.ACTION_DOWN);
|
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
return true;
|
ClipData clipData = clipboard.getPrimaryClip();
|
||||||
} else if (!mEmulator.isMouseTrackingActive() && action == MotionEvent.ACTION_DOWN) {
|
if (clipData != null) {
|
||||||
// Start text selection with mouse. Note that the check against MotionEvent.ACTION_DOWN is
|
CharSequence paste = clipData.getItemAt(0).coerceToText(getContext());
|
||||||
// important, since we otherwise would pick up secondary mouse button up actions.
|
if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString());
|
||||||
mIsSelectingText = true;
|
|
||||||
}
|
}
|
||||||
} else if (!mIsSelectingText) {
|
} else if (mEmulator.isMouseTrackingActive()) { // BUTTON_PRIMARY.
|
||||||
|
switch (ev.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
sendMouseEventCode(ev, TerminalEmulator.MOUSE_LEFT_BUTTON, ev.getAction() == MotionEvent.ACTION_DOWN);
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
sendMouseEventCode(ev, TerminalEmulator.MOUSE_LEFT_BUTTON_MOVED, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mGestureRecognizer.onTouchEvent(ev);
|
mGestureRecognizer.onTouchEvent(ev);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mIsSelectingText) {
|
|
||||||
int cx = (int) (ev.getX() / mRenderer.mFontWidth);
|
|
||||||
// Offset for finger:
|
|
||||||
final int SELECT_TEXT_OFFSET_Y = eventFromMouse ? 0 : -40;
|
|
||||||
int cy = (int) ((ev.getY() + SELECT_TEXT_OFFSET_Y) / mRenderer.mFontLineSpacing) + mTopRow;
|
|
||||||
switch (action) {
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
mSelXAnchor = cx;
|
|
||||||
mSelYAnchor = cy;
|
|
||||||
mSelX1 = cx;
|
|
||||||
mSelY1 = cy;
|
|
||||||
mSelX2 = mSelX1;
|
|
||||||
mSelY2 = mSelY1;
|
|
||||||
invalidate();
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
boolean touchBeforeAnchor = (cy < mSelYAnchor || (cy == mSelYAnchor && cx < mSelXAnchor));
|
|
||||||
int minx = touchBeforeAnchor ? cx : mSelXAnchor;
|
|
||||||
int maxx = !touchBeforeAnchor ? cx : mSelXAnchor;
|
|
||||||
int miny = touchBeforeAnchor ? cy : mSelYAnchor;
|
|
||||||
int maxy = !touchBeforeAnchor ? cy : mSelYAnchor;
|
|
||||||
mSelX1 = minx;
|
|
||||||
mSelY1 = miny;
|
|
||||||
mSelX2 = maxx;
|
|
||||||
mSelY2 = maxy;
|
|
||||||
if (action == MotionEvent.ACTION_UP) {
|
|
||||||
String selectedText = mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2).trim();
|
|
||||||
mTermSession.clipboardText(selectedText);
|
|
||||||
toggleSelectingText();
|
|
||||||
}
|
|
||||||
invalidate();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
toggleSelectingText();
|
|
||||||
invalidate();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
|
if (LOG_KEY_EVENTS)
|
||||||
if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) {
|
Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
|
||||||
// Handle the escape key ourselves to avoid the system from treating it as back key
|
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
// and e.g. close keyboard.
|
if (mIsSelectingText) {
|
||||||
|
toggleSelectingText(null);
|
||||||
|
return true;
|
||||||
|
} else if (mOnKeyListener.shouldBackButtonBeMappedToEscape()) {
|
||||||
|
// Intercept back button to treat it as escape:
|
||||||
switch (event.getAction()) {
|
switch (event.getAction()) {
|
||||||
case KeyEvent.ACTION_DOWN:
|
case KeyEvent.ACTION_DOWN:
|
||||||
return onKeyDown(keyCode, event);
|
return onKeyDown(keyCode, event);
|
||||||
@@ -502,26 +556,31 @@ public final class TerminalView extends View {
|
|||||||
return onKeyUp(keyCode, event);
|
return onKeyUp(keyCode, event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return super.onKeyPreIme(keyCode, event);
|
return super.onKeyPreIme(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")");
|
if (LOG_KEY_EVENTS)
|
||||||
|
Log.i(EmulatorDebug.LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")");
|
||||||
if (mEmulator == null) return true;
|
if (mEmulator == null) return true;
|
||||||
|
|
||||||
int metaState = event.getMetaState();
|
if (mOnKeyListener.onKeyDown(keyCode, event, mTermSession)) {
|
||||||
boolean controlDownFromEvent = event.isCtrlPressed();
|
|
||||||
boolean leftAltDownFromEvent = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0;
|
|
||||||
boolean rightAltDownFromEvent = (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0;
|
|
||||||
|
|
||||||
if (handleVirtualKeys(keyCode, event, true)) {
|
|
||||||
invalidate();
|
invalidate();
|
||||||
return true;
|
return true;
|
||||||
} else if (event.isSystem() && keyCode != KeyEvent.KEYCODE_BACK) {
|
} else if (event.isSystem() && (!mOnKeyListener.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) {
|
||||||
return super.onKeyDown(keyCode, event);
|
return super.onKeyDown(keyCode, event);
|
||||||
|
} else if (event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) {
|
||||||
|
mTermSession.write(event.getCharacters());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final int metaState = event.getMetaState();
|
||||||
|
final boolean controlDownFromEvent = event.isCtrlPressed();
|
||||||
|
final boolean leftAltDownFromEvent = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0;
|
||||||
|
final boolean rightAltDownFromEvent = (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0;
|
||||||
|
|
||||||
int keyMod = 0;
|
int keyMod = 0;
|
||||||
if (controlDownFromEvent) keyMod |= KeyHandler.KEYMOD_CTRL;
|
if (controlDownFromEvent) keyMod |= KeyHandler.KEYMOD_CTRL;
|
||||||
if (event.isAltPressed()) keyMod |= KeyHandler.KEYMOD_ALT;
|
if (event.isAltPressed()) keyMod |= KeyHandler.KEYMOD_ALT;
|
||||||
@@ -542,7 +601,8 @@ public final class TerminalView extends View {
|
|||||||
int effectiveMetaState = event.getMetaState() & ~bitsToClear;
|
int effectiveMetaState = event.getMetaState() & ~bitsToClear;
|
||||||
|
|
||||||
int result = event.getUnicodeChar(effectiveMetaState);
|
int result = event.getUnicodeChar(effectiveMetaState);
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result);
|
if (LOG_KEY_EVENTS)
|
||||||
|
Log.i(EmulatorDebug.LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result);
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -550,7 +610,8 @@ public final class TerminalView extends View {
|
|||||||
int oldCombiningAccent = mCombiningAccent;
|
int oldCombiningAccent = mCombiningAccent;
|
||||||
if ((result & KeyCharacterMap.COMBINING_ACCENT) != 0) {
|
if ((result & KeyCharacterMap.COMBINING_ACCENT) != 0) {
|
||||||
// If entered combining accent previously, write it out:
|
// If entered combining accent previously, write it out:
|
||||||
if (mCombiningAccent != 0) inputCodePoint(mCombiningAccent, controlDownFromEvent, leftAltDownFromEvent);
|
if (mCombiningAccent != 0)
|
||||||
|
inputCodePoint(mCombiningAccent, controlDownFromEvent, leftAltDownFromEvent);
|
||||||
mCombiningAccent = result & KeyCharacterMap.COMBINING_ACCENT_MASK;
|
mCombiningAccent = result & KeyCharacterMap.COMBINING_ACCENT_MASK;
|
||||||
} else {
|
} else {
|
||||||
if (mCombiningAccent != 0) {
|
if (mCombiningAccent != 0) {
|
||||||
@@ -572,8 +633,12 @@ public final class TerminalView extends View {
|
|||||||
+ leftAltDownFromEvent + ")");
|
+ leftAltDownFromEvent + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
int resultingKeyCode = -1; // Set if virtual key causes this to be translated to key event.
|
final boolean controlDown = controlDownFromEvent || mOnKeyListener.readControlKey();
|
||||||
if (controlDownFromEvent || mVirtualControlKeyDown) {
|
final boolean altDown = leftAltDownFromEvent || mOnKeyListener.readAltKey();
|
||||||
|
|
||||||
|
if (mOnKeyListener.onCodePoint(codePoint, controlDown, mTermSession)) return;
|
||||||
|
|
||||||
|
if (controlDown) {
|
||||||
if (codePoint >= 'a' && codePoint <= 'z') {
|
if (codePoint >= 'a' && codePoint <= 'z') {
|
||||||
codePoint = codePoint - 'a' + 1;
|
codePoint = codePoint - 'a' + 1;
|
||||||
} else if (codePoint >= 'A' && codePoint <= 'Z') {
|
} else if (codePoint >= 'A' && codePoint <= 'Z') {
|
||||||
@@ -588,75 +653,33 @@ public final class TerminalView extends View {
|
|||||||
codePoint = 29;
|
codePoint = 29;
|
||||||
} else if (codePoint == '^' || codePoint == '6') {
|
} else if (codePoint == '^' || codePoint == '6') {
|
||||||
codePoint = 30; // control-^
|
codePoint = 30; // control-^
|
||||||
} else if (codePoint == '_' || codePoint == '7') {
|
} else if (codePoint == '_' || codePoint == '7' || codePoint == '/') {
|
||||||
|
// "Ctrl-/ sends 0x1f which is equivalent of Ctrl-_ since the days of VT102"
|
||||||
|
// - http://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal
|
||||||
codePoint = 31;
|
codePoint = 31;
|
||||||
} else if (codePoint == '8') {
|
} else if (codePoint == '8') {
|
||||||
codePoint = 127; // DEL
|
codePoint = 127; // DEL
|
||||||
} else if (codePoint == '9') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_F11;
|
|
||||||
} else if (codePoint == '0') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_F12;
|
|
||||||
}
|
|
||||||
} else if (mVirtualFnKeyDown) {
|
|
||||||
if (codePoint == 'w' || codePoint == 'W') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP;
|
|
||||||
} else if (codePoint == 'a' || codePoint == 'A') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT;
|
|
||||||
} else if (codePoint == 's' || codePoint == 'S') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN;
|
|
||||||
} else if (codePoint == 'd' || codePoint == 'D') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
|
|
||||||
} else if (codePoint == 'p' || codePoint == 'P') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP;
|
|
||||||
} else if (codePoint == 'n' || codePoint == 'N') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN;
|
|
||||||
} else if (codePoint == 't' || codePoint == 'T') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_TAB;
|
|
||||||
} else if (codePoint == 'l' || codePoint == 'L') {
|
|
||||||
codePoint = '|';
|
|
||||||
} else if (codePoint == 'u' || codePoint == 'U') {
|
|
||||||
codePoint = '_';
|
|
||||||
} else if (codePoint == 'e' || codePoint == 'E') {
|
|
||||||
codePoint = 27; // ^[ (Esc)
|
|
||||||
} else if (codePoint == '.') {
|
|
||||||
codePoint = 28; // ^\
|
|
||||||
} else if (codePoint > '0' && codePoint <= '9') {
|
|
||||||
// F1-F9
|
|
||||||
resultingKeyCode = (codePoint - '1') + KeyEvent.KEYCODE_F1;
|
|
||||||
} else if (codePoint == '0') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_F10;
|
|
||||||
} else if (codePoint == 'i' || codePoint == 'I') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_INSERT;
|
|
||||||
} else if (codePoint == 'x' || codePoint == 'X') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_FORWARD_DEL;
|
|
||||||
} else if (codePoint == 'h' || codePoint == 'H') {
|
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_MOVE_HOME;
|
|
||||||
} else if (codePoint == 'f' || codePoint == 'F') {
|
|
||||||
// As left alt+f, jumping forward in readline:
|
|
||||||
codePoint = 'f';
|
|
||||||
leftAltDownFromEvent = true;
|
|
||||||
} else if (codePoint == 'b' || codePoint == 'B') {
|
|
||||||
// As left alt+b, jumping forward in readline:
|
|
||||||
codePoint = 'b';
|
|
||||||
leftAltDownFromEvent = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codePoint > -1) {
|
if (codePoint > -1) {
|
||||||
if (resultingKeyCode > -1) {
|
// Work around bluetooth keyboards sending funny unicode characters instead
|
||||||
handleKeyCode(resultingKeyCode, 0);
|
// of the more normal ones from ASCII that terminal programs expect - the
|
||||||
} else {
|
// desire to input the original characters should be low.
|
||||||
// The below two workarounds are needed on at least Logitech Keyboard k810 on Samsung Galaxy Tab Pro
|
switch (codePoint) {
|
||||||
// (Android 4.4) with the stock Samsung Keyboard. They should be harmless when not used since the need
|
case 0x02DC: // SMALL TILDE.
|
||||||
// to input the original characters instead of the new ones using the keyboard should be low.
|
codePoint = 0x007E; // TILDE (~).
|
||||||
// Rewrite U+02DC 'SMALL TILDE' to U+007E 'TILDE' for ~ to work in shells:
|
break;
|
||||||
if (codePoint == 0x02DC) codePoint = 0x07E;
|
case 0x02CB: // MODIFIER LETTER GRAVE ACCENT.
|
||||||
// Rewrite U+02CB 'MODIFIER LETTER GRAVE ACCENT' to U+0060 'GRAVE ACCENT' for ` (backticks) to work:
|
codePoint = 0x0060; // GRAVE ACCENT (`).
|
||||||
if (codePoint == 0x02CB) codePoint = 0x60;
|
break;
|
||||||
|
case 0x02C6: // MODIFIER LETTER CIRCUMFLEX ACCENT.
|
||||||
|
codePoint = 0x005E; // CIRCUMFLEX ACCENT (^).
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// If left alt, send escape before the code point to make e.g. Alt+B and Alt+F work in readline:
|
// If left alt, send escape before the code point to make e.g. Alt+B and Alt+F work in readline:
|
||||||
mTermSession.writeCodePoint(leftAltDownFromEvent, codePoint);
|
mTermSession.writeCodePoint(altDown, codePoint);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,18 +695,17 @@ public final class TerminalView extends View {
|
|||||||
/**
|
/**
|
||||||
* Called when a key is released in the view.
|
* Called when a key is released in the view.
|
||||||
*
|
*
|
||||||
* @param keyCode
|
* @param keyCode The keycode of the key which was released.
|
||||||
* The keycode of the key which was released.
|
* @param event A {@link KeyEvent} describing the event.
|
||||||
* @param event
|
|
||||||
* A {@link KeyEvent} describing the event.
|
|
||||||
* @return Whether the event was handled.
|
* @return Whether the event was handled.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
|
if (LOG_KEY_EVENTS)
|
||||||
|
Log.i(EmulatorDebug.LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
|
||||||
if (mEmulator == null) return true;
|
if (mEmulator == null) return true;
|
||||||
|
|
||||||
if (handleVirtualKeys(keyCode, event, false)) {
|
if (mOnKeyListener.onKeyUp(keyCode, event)) {
|
||||||
invalidate();
|
invalidate();
|
||||||
return true;
|
return true;
|
||||||
} else if (event.isSystem()) {
|
} else if (event.isSystem()) {
|
||||||
@@ -694,87 +716,6 @@ public final class TerminalView extends View {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Handle dedicated volume buttons as virtual keys if applicable. */
|
|
||||||
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
|
|
||||||
InputDevice inputDevice = event.getDevice();
|
|
||||||
if (inputDevice != null && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
|
||||||
// Do not steal dedicated buttons from a full external keyboard.
|
|
||||||
return false;
|
|
||||||
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "handleVirtualKeys(down=" + down + ") taking ctrl event");
|
|
||||||
mVirtualControlKeyDown = down;
|
|
||||||
return true;
|
|
||||||
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "handleVirtualKeys(down=" + down + ") taking Fn event");
|
|
||||||
mVirtualFnKeyDown = down;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkForTypeface() {
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
File fontFile = new File(getContext().getFilesDir().getPath() + "/home/.termux/font.ttf");
|
|
||||||
final Typeface newTypeface = fontFile.exists() ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
|
|
||||||
if (newTypeface != mRenderer.mTypeface) {
|
|
||||||
((Activity) getContext()).runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface);
|
|
||||||
updateSize();
|
|
||||||
invalidate();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(EmulatorDebug.LOG_TAG, "Error loading font", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(EmulatorDebug.LOG_TAG, "Error loading font", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkForColors() {
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
File colorsFile = new File(getContext().getFilesDir().getPath() + "/home/.termux/colors.properties");
|
|
||||||
final Properties props = colorsFile.isFile() ? new Properties() : null;
|
|
||||||
if (props != null) {
|
|
||||||
try (InputStream in = new FileInputStream(colorsFile)) {
|
|
||||||
props.load(in);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
((Activity) getContext()).runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
if (props == null) {
|
|
||||||
TerminalColors.COLOR_SCHEME.reset();
|
|
||||||
} else {
|
|
||||||
TerminalColors.COLOR_SCHEME.updateWith(props);
|
|
||||||
}
|
|
||||||
if (mEmulator != null) mEmulator.mColors.reset();
|
|
||||||
invalidate();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(EmulatorDebug.LOG_TAG, "Setting colors failed: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(EmulatorDebug.LOG_TAG, "Failed colors handling", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is called during layout when the size of this view has changed. If you were just added to the view
|
* This is called during layout when the size of this view has changed. If you were just added to the view
|
||||||
* hierarchy, you're called with the old values of 0.
|
* hierarchy, you're called with the old values of 0.
|
||||||
@@ -791,8 +732,8 @@ public final class TerminalView extends View {
|
|||||||
if (viewWidth == 0 || viewHeight == 0 || mTermSession == null) return;
|
if (viewWidth == 0 || viewHeight == 0 || mTermSession == null) return;
|
||||||
|
|
||||||
// Set to 80 and 24 if you want to enable vttest.
|
// Set to 80 and 24 if you want to enable vttest.
|
||||||
int newColumns = Math.max(8, (int) (viewWidth / mRenderer.mFontWidth));
|
int newColumns = Math.max(4, (int) (viewWidth / mRenderer.mFontWidth));
|
||||||
int newRows = Math.max(8, (viewHeight - mRenderer.mFontLineSpacingAndAscent) / mRenderer.mFontLineSpacing);
|
int newRows = Math.max(4, (viewHeight - mRenderer.mFontLineSpacingAndAscent) / mRenderer.mFontLineSpacing);
|
||||||
|
|
||||||
if (mEmulator == null || (newColumns != mEmulator.mColumns || newRows != mEmulator.mRows)) {
|
if (mEmulator == null || (newColumns != mEmulator.mColumns || newRows != mEmulator.mRows)) {
|
||||||
mTermSession.updateSize(newColumns, newRows);
|
mTermSession.updateSize(newColumns, newRows);
|
||||||
@@ -810,13 +751,149 @@ public final class TerminalView extends View {
|
|||||||
canvas.drawColor(0XFF000000);
|
canvas.drawColor(0XFF000000);
|
||||||
} else {
|
} else {
|
||||||
mRenderer.render(mEmulator, canvas, mTopRow, mSelY1, mSelY2, mSelX1, mSelX2);
|
mRenderer.render(mEmulator, canvas, mTopRow, mSelY1, mSelY2, mSelX1, mSelX2);
|
||||||
|
|
||||||
|
if (mIsSelectingText) {
|
||||||
|
final int gripHandleWidth = mLeftSelectionHandle.getIntrinsicWidth();
|
||||||
|
final int gripHandleMargin = gripHandleWidth / 4; // See the png.
|
||||||
|
|
||||||
|
int right = Math.round((mSelX1) * mRenderer.mFontWidth) + gripHandleMargin;
|
||||||
|
int top = (mSelY1 + 1 - mTopRow) * mRenderer.mFontLineSpacing + mRenderer.mFontLineSpacingAndAscent;
|
||||||
|
mLeftSelectionHandle.setBounds(right - gripHandleWidth, top, right, top + mLeftSelectionHandle.getIntrinsicHeight());
|
||||||
|
mLeftSelectionHandle.draw(canvas);
|
||||||
|
|
||||||
|
int left = Math.round((mSelX2 + 1) * mRenderer.mFontWidth) - gripHandleMargin;
|
||||||
|
top = (mSelY2 + 1 - mTopRow) * mRenderer.mFontLineSpacing + mRenderer.mFontLineSpacingAndAscent;
|
||||||
|
mRightSelectionHandle.setBounds(left, top, left + gripHandleWidth, top + mRightSelectionHandle.getIntrinsicHeight());
|
||||||
|
mRightSelectionHandle.draw(canvas);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Toggle text selection mode in the view. */
|
/** Toggle text selection mode in the view. */
|
||||||
public void toggleSelectingText() {
|
@TargetApi(23)
|
||||||
|
public void toggleSelectingText(MotionEvent ev) {
|
||||||
mIsSelectingText = !mIsSelectingText;
|
mIsSelectingText = !mIsSelectingText;
|
||||||
if (!mIsSelectingText) mSelX1 = mSelY1 = mSelX2 = mSelY2 = -1;
|
mOnKeyListener.copyModeChanged(mIsSelectingText);
|
||||||
|
|
||||||
|
if (mIsSelectingText) {
|
||||||
|
if (mLeftSelectionHandle == null) {
|
||||||
|
mLeftSelectionHandle = (BitmapDrawable) getContext().getDrawable(R.drawable.text_select_handle_left_material);
|
||||||
|
mRightSelectionHandle = (BitmapDrawable) getContext().getDrawable(R.drawable.text_select_handle_right_material);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cx = (int) (ev.getX() / mRenderer.mFontWidth);
|
||||||
|
final boolean eventFromMouse = ev.isFromSource(InputDevice.SOURCE_MOUSE);
|
||||||
|
// Offset for finger:
|
||||||
|
final int SELECT_TEXT_OFFSET_Y = eventFromMouse ? 0 : -40;
|
||||||
|
int cy = (int) ((ev.getY() + SELECT_TEXT_OFFSET_Y) / mRenderer.mFontLineSpacing) + mTopRow;
|
||||||
|
|
||||||
|
mSelX1 = mSelX2 = cx;
|
||||||
|
mSelY1 = mSelY2 = cy;
|
||||||
|
|
||||||
|
TerminalBuffer screen = mEmulator.getScreen();
|
||||||
|
if (!" ".equals(screen.getSelectedText(mSelX1, mSelY1, mSelX1, mSelY1))) {
|
||||||
|
// Selecting something other than whitespace. Expand to word.
|
||||||
|
while (mSelX1 > 0 && !"".equals(screen.getSelectedText(mSelX1 - 1, mSelY1, mSelX1 - 1, mSelY1))) {
|
||||||
|
mSelX1--;
|
||||||
|
}
|
||||||
|
while (mSelX2 < mEmulator.mColumns - 1 && !"".equals(screen.getSelectedText(mSelX2 + 1, mSelY1, mSelX2 + 1, mSelY1))) {
|
||||||
|
mSelX2++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mInitialTextSelection = true;
|
||||||
|
mIsDraggingLeftSelection = true;
|
||||||
|
mSelectionDownX = ev.getX();
|
||||||
|
mSelectionDownY = ev.getY();
|
||||||
|
|
||||||
|
final ActionMode.Callback callback = new ActionMode.Callback() {
|
||||||
|
@Override
|
||||||
|
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||||
|
int show = MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT;
|
||||||
|
|
||||||
|
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
menu.add(Menu.NONE, 1, Menu.NONE, R.string.copy_text).setShowAsAction(show);
|
||||||
|
menu.add(Menu.NONE, 2, Menu.NONE, R.string.paste_text).setEnabled(clipboard.hasPrimaryClip()).setShowAsAction(show);
|
||||||
|
menu.add(Menu.NONE, 3, Menu.NONE, R.string.text_selection_more);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case 1:
|
||||||
|
String selectedText = mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2).trim();
|
||||||
|
mTermSession.clipboardText(selectedText);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
ClipData clipData = clipboard.getPrimaryClip();
|
||||||
|
if (clipData != null) {
|
||||||
|
CharSequence paste = clipData.getItemAt(0).coerceToText(getContext());
|
||||||
|
if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
showContextMenu();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
toggleSelectingText(null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyActionMode(ActionMode mode) {
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
mActionMode = startActionMode(new ActionMode.Callback2() {
|
||||||
|
@Override
|
||||||
|
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||||
|
return callback.onCreateActionMode(mode, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||||
|
return callback.onActionItemClicked(mode, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyActionMode(ActionMode mode) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
|
||||||
|
int x1 = Math.round(mSelX1 * mRenderer.mFontWidth);
|
||||||
|
int x2 = Math.round(mSelX2 * mRenderer.mFontWidth);
|
||||||
|
int y1 = Math.round((mSelY1 - mTopRow) * mRenderer.mFontLineSpacing);
|
||||||
|
int y2 = Math.round((mSelY2 + 1 - mTopRow) * mRenderer.mFontLineSpacing);
|
||||||
|
outRect.set(Math.min(x1, x2), y1, Math.max(x1, x2), y2);
|
||||||
|
}
|
||||||
|
}, ActionMode.TYPE_FLOATING);
|
||||||
|
} else {
|
||||||
|
mActionMode = startActionMode(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
} else {
|
||||||
|
mActionMode.finish();
|
||||||
|
mSelX1 = mSelY1 = mSelX2 = mSelY2 = -1;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TerminalSession getCurrentSession() {
|
public TerminalSession getCurrentSession() {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
LOCAL_PATH:= $(call my-dir)
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
LOCAL_MODULE:= libtermux
|
|
||||||
LOCAL_SRC_FILES:= termux.c
|
|
||||||
include $(BUILD_SHARED_LIBRARY)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
APP_ABI := armeabi-v7a x86
|
|
||||||
APP_PLATFORM := android-21
|
|
||||||
NDK_TOOLCHAIN_VERSION := 4.9
|
|
||||||
APP_CFLAGS := -std=c11 -Wall -Wextra -Os -fno-stack-protector
|
|
||||||
APP_LDFLAGS = -nostdlib -Wl,--gc-sections
|
|
||||||
@@ -22,7 +22,14 @@ static int throw_runtime_exception(JNIEnv* env, char const* message)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int create_subprocess(JNIEnv* env, char const* cmd, char const* cwd, char* const argv[], char** envp, int* pProcessId)
|
static int create_subprocess(JNIEnv* env,
|
||||||
|
char const* cmd,
|
||||||
|
char const* cwd,
|
||||||
|
char* const argv[],
|
||||||
|
char** envp,
|
||||||
|
int* pProcessId,
|
||||||
|
jint rows,
|
||||||
|
jint columns)
|
||||||
{
|
{
|
||||||
int ptm = open("/dev/ptmx", O_RDWR | O_CLOEXEC);
|
int ptm = open("/dev/ptmx", O_RDWR | O_CLOEXEC);
|
||||||
if (ptm < 0) return throw_runtime_exception(env, "Cannot open /dev/ptmx");
|
if (ptm < 0) return throw_runtime_exception(env, "Cannot open /dev/ptmx");
|
||||||
@@ -49,8 +56,8 @@ static int create_subprocess(JNIEnv* env, char const* cmd, char const* cwd, char
|
|||||||
tios.c_iflag &= ~(IXON | IXOFF);
|
tios.c_iflag &= ~(IXON | IXOFF);
|
||||||
tcsetattr(ptm, TCSANOW, &tios);
|
tcsetattr(ptm, TCSANOW, &tios);
|
||||||
|
|
||||||
/** Set initial winsize (better too small than too large). */
|
/** Set initial winsize. */
|
||||||
struct winsize sz = { .ws_row = 20, .ws_col = 20 };
|
struct winsize sz = { .ws_row = rows, .ws_col = columns };
|
||||||
ioctl(ptm, TIOCSWINSZ, &sz);
|
ioctl(ptm, TIOCSWINSZ, &sz);
|
||||||
|
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
@@ -105,7 +112,16 @@ static int create_subprocess(JNIEnv* env, char const* cmd, char const* cwd, char
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(JNIEnv* env, jclass TERMUX_UNUSED(clazz), jstring cmd, jstring cwd, jobjectArray args, jobjectArray envVars, jintArray processIdArray)
|
JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(
|
||||||
|
JNIEnv* env,
|
||||||
|
jclass TERMUX_UNUSED(clazz),
|
||||||
|
jstring cmd,
|
||||||
|
jstring cwd,
|
||||||
|
jobjectArray args,
|
||||||
|
jobjectArray envVars,
|
||||||
|
jintArray processIdArray,
|
||||||
|
jint rows,
|
||||||
|
jint columns)
|
||||||
{
|
{
|
||||||
jsize size = args ? (*env)->GetArrayLength(env, args) : 0;
|
jsize size = args ? (*env)->GetArrayLength(env, args) : 0;
|
||||||
char** argv = NULL;
|
char** argv = NULL;
|
||||||
@@ -140,7 +156,7 @@ JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(JNIEnv* env
|
|||||||
int procId = 0;
|
int procId = 0;
|
||||||
char const* cmd_cwd = (*env)->GetStringUTFChars(env, cwd, NULL);
|
char const* cmd_cwd = (*env)->GetStringUTFChars(env, cwd, NULL);
|
||||||
char const* cmd_utf8 = (*env)->GetStringUTFChars(env, cmd, NULL);
|
char const* cmd_utf8 = (*env)->GetStringUTFChars(env, cmd, NULL);
|
||||||
int ptm = create_subprocess(env, cmd_utf8, cmd_cwd, argv, envp, &procId);
|
int ptm = create_subprocess(env, cmd_utf8, cmd_cwd, argv, envp, &procId, rows, columns);
|
||||||
(*env)->ReleaseStringUTFChars(env, cmd, cmd_utf8);
|
(*env)->ReleaseStringUTFChars(env, cmd, cmd_utf8);
|
||||||
(*env)->ReleaseStringUTFChars(env, cmd, cmd_cwd);
|
(*env)->ReleaseStringUTFChars(env, cmd, cmd_cwd);
|
||||||
|
|
||||||
@@ -192,11 +208,6 @@ JNIEXPORT int JNICALL Java_com_termux_terminal_JNI_waitFor(JNIEnv* TERMUX_UNUSED
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_hangupProcessGroup(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint procId)
|
|
||||||
{
|
|
||||||
killpg(procId, SIGHUP);
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_close(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fileDescriptor)
|
JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_close(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fileDescriptor)
|
||||||
{
|
{
|
||||||
close(fileDescriptor);
|
close(fileDescriptor);
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:src="@drawable/text_select_handle_left_mtrl_alpha"
|
||||||
|
android:tint="#2196F3" />
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:src="@drawable/text_select_handle_right_mtrl_alpha"
|
||||||
|
android:tint="#2196F3" />
|
||||||
@@ -1,7 +1,14 @@
|
|||||||
<com.termux.drawer.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<android.support.v4.widget.DrawerLayout
|
||||||
android:id="@+id/drawer_layout"
|
android:id="@+id/drawer_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" >
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_above="@+id/viewpager"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.termux.view.TerminalView
|
<com.termux.view.TerminalView
|
||||||
android:id="@+id/terminal_view"
|
android:id="@+id/terminal_view"
|
||||||
@@ -20,7 +27,8 @@
|
|||||||
android:choiceMode="singleChoice"
|
android:choiceMode="singleChoice"
|
||||||
android:divider="@android:color/transparent"
|
android:divider="@android:color/transparent"
|
||||||
android:dividerHeight="0dp"
|
android:dividerHeight="0dp"
|
||||||
android:orientation="vertical" >
|
android:descendantFocusability="blocksDescendants"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ListView
|
<ListView
|
||||||
android:id="@+id/left_drawer_list"
|
android:id="@+id/left_drawer_list"
|
||||||
@@ -35,7 +43,7 @@
|
|||||||
style="?android:attr/buttonBarStyle"
|
style="?android:attr/buttonBarStyle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal" >
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/toggle_keyboard_button"
|
android:id="@+id/toggle_keyboard_button"
|
||||||
@@ -55,4 +63,13 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</com.termux.drawer.DrawerLayout>
|
</android.support.v4.widget.DrawerLayout>
|
||||||
|
|
||||||
|
<android.support.v4.view.ViewPager
|
||||||
|
android:id="@+id/viewpager"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:background="@android:drawable/screen_background_dark_transparent"
|
||||||
|
android:layout_alignParentBottom="true" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|||||||
8
app/src/main/res/layout/extra_keys_main.xml
Normal file
8
app/src/main/res/layout/extra_keys_main.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.termux.app.ExtraKeysView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/extra_keys"
|
||||||
|
style="?android:attr/buttonBarStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:orientation="horizontal" />
|
||||||
15
app/src/main/res/layout/extra_keys_right.xml
Normal file
15
app/src/main/res/layout/extra_keys_right.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/text_input"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:imeOptions="actionSend|flagNoFullscreen"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:inputType="text"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:paddingTop="0dp"
|
||||||
|
android:textCursorDrawable="@null"
|
||||||
|
android:paddingBottom="0dp"
|
||||||
|
tools:ignore="LabelFor" />
|
||||||
BIN
app/src/main/res/raw/bell.ogg
Normal file
BIN
app/src/main/res/raw/bell.ogg
Normal file
Binary file not shown.
@@ -1,11 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="application_name">Termux</string>
|
<string name="application_name">Termux</string>
|
||||||
<string name="application_help">Termux help</string>
|
|
||||||
<string name="shared_user_label">Termux user</string>
|
<string name="shared_user_label">Termux user</string>
|
||||||
<string name="new_session">New session</string>
|
<string name="new_session">New session</string>
|
||||||
<string name="new_session_normal_unnamed">Normal - unnamed</string>
|
|
||||||
<string name="new_session_normal_named">Normal - named</string>
|
|
||||||
<string name="new_session_failsafe">Failsafe</string>
|
<string name="new_session_failsafe">Failsafe</string>
|
||||||
<string name="toggle_soft_keyboard">Keyboard</string>
|
<string name="toggle_soft_keyboard">Keyboard</string>
|
||||||
<string name="reset_terminal">Reset</string>
|
<string name="reset_terminal">Reset</string>
|
||||||
@@ -15,7 +12,7 @@
|
|||||||
<string name="help">Help</string>
|
<string name="help">Help</string>
|
||||||
|
|
||||||
<string name="welcome_dialog_title">Welcome to Termux</string>
|
<string name="welcome_dialog_title">Welcome to Termux</string>
|
||||||
<string name="welcome_dialog_body">Long press anywhere on the terminal for a context menu where Help is available.\n\nExecute \'apt update\' to update the packages list before installing packages.</string>
|
<string name="welcome_dialog_body">Long press and select <i>More…</i> to show a menu where <i>Help</i> is available.\n\nExecute <b>apt update</b> to update the packages list before installing packages.</string>
|
||||||
<string name="welcome_dialog_dont_show_again_button">Do not show again</string>
|
<string name="welcome_dialog_dont_show_again_button">Do not show again</string>
|
||||||
|
|
||||||
<string name="bootstrap_installer_body">Installing…</string>
|
<string name="bootstrap_installer_body">Installing…</string>
|
||||||
@@ -30,19 +27,19 @@
|
|||||||
|
|
||||||
<string name="reset_toast_notification">Terminal reset.</string>
|
<string name="reset_toast_notification">Terminal reset.</string>
|
||||||
|
|
||||||
<string name="select">Select…</string>
|
|
||||||
<string name="select_text">Select text</string>
|
|
||||||
<string name="select_url">Select URL</string>
|
<string name="select_url">Select URL</string>
|
||||||
<string name="select_url_dialog_title">Click URL to copy or long press to open</string>
|
<string name="select_url_dialog_title">Click URL to copy or long press to open</string>
|
||||||
<string name="select_all_and_share">Select all text and share</string>
|
<string name="select_all_and_share">Share transcript</string>
|
||||||
<string name="select_url_no_found">No URL found in the terminal.</string>
|
<string name="select_url_no_found">No URL found in the terminal.</string>
|
||||||
<string name="select_url_copied_to_clipboard">URL copied to clipboard</string>
|
<string name="select_url_copied_to_clipboard">URL copied to clipboard</string>
|
||||||
<string name="share_transcript_chooser_title">Send text to:</string>
|
<string name="share_transcript_chooser_title">Send text to:</string>
|
||||||
|
|
||||||
<string name="paste_text">Paste</string>
|
<string name="paste_text">Paste</string>
|
||||||
<string name="kill_process">Hangup</string>
|
<string name="copy_text">Copy</string>
|
||||||
|
<string name="text_selection_more">More…</string>
|
||||||
|
|
||||||
<string name="confirm_kill_process">Close this process?</string>
|
<string name="kill_process">Kill process (%d)</string>
|
||||||
|
<string name="confirm_kill_process">Really kill this session?</string>
|
||||||
|
|
||||||
<string name="session_rename_title">Set session name</string>
|
<string name="session_rename_title">Set session name</string>
|
||||||
<string name="session_rename_positive_button">Set</string>
|
<string name="session_rename_positive_button">Set</string>
|
||||||
@@ -56,4 +53,8 @@
|
|||||||
<string name="notification_action_wakelock">Wake</string>
|
<string name="notification_action_wakelock">Wake</string>
|
||||||
<string name="notification_action_wifilock">Wifi</string>
|
<string name="notification_action_wifilock">Wifi</string>
|
||||||
|
|
||||||
|
<string name="file_received_title">Save file in ~/downloads/</string>
|
||||||
|
<string name="file_received_edit_button">Edit</string>
|
||||||
|
<string name="file_received_open_folder_button">Open folder</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
<!-- Seen in buttons on left drawer: -->
|
<!-- Seen in buttons on left drawer: -->
|
||||||
<item name="android:colorAccent">#212121</item>
|
<item name="android:colorAccent">#212121</item>
|
||||||
<item name="android:alertDialogTheme">@style/TermuxAlertDialogStyle</item>
|
<item name="android:alertDialogTheme">@style/TermuxAlertDialogStyle</item>
|
||||||
|
<!-- Avoid action mode toolbar pushing down terminal content when
|
||||||
|
selecting text on pre-6.0 (non-floating toolbar). -->
|
||||||
|
<item name="android:windowActionModeOverlay">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
|
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
|
||||||
|
|||||||
5
app/src/main/res/xml/backupscheme.xml
Normal file
5
app/src/main/res/xml/backupscheme.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<full-backup-content>
|
||||||
|
<!-- See https://developer.android.com/training/backup/autosyncapi.html -->
|
||||||
|
<include domain="file" path="home/backup" />
|
||||||
|
</full-backup-content>
|
||||||
25
app/src/test/java/com/termux/app/TermuxActivityTest.java
Normal file
25
app/src/test/java/com/termux/app/TermuxActivityTest.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package com.termux.app;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
|
||||||
|
public class TermuxActivityTest extends TestCase {
|
||||||
|
|
||||||
|
private void assertUrlsAre(String text, String... urls) {
|
||||||
|
LinkedHashSet<String> expected = new LinkedHashSet<>();
|
||||||
|
Collections.addAll(expected, urls);
|
||||||
|
assertEquals(expected, TermuxActivity.extractUrls(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testExtractUrls() {
|
||||||
|
assertUrlsAre("hello http://example.com world", "http://example.com");
|
||||||
|
|
||||||
|
assertUrlsAre("http://example.com\nhttp://another.com", "http://example.com", "http://another.com");
|
||||||
|
|
||||||
|
assertUrlsAre("hello http://example.com world and http://more.example.com with secure https://more.example.com",
|
||||||
|
"http://example.com", "http://more.example.com", "https://more.example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,21 +17,21 @@ public class ByteQueueTest extends TestCase {
|
|||||||
|
|
||||||
public void testCompleteWrites() throws Exception {
|
public void testCompleteWrites() throws Exception {
|
||||||
ByteQueue q = new ByteQueue(10);
|
ByteQueue q = new ByteQueue(10);
|
||||||
assertEquals(true, q.write(new byte[] { 1, 2, 3 }, 0, 3));
|
assertEquals(true, q.write(new byte[]{1, 2, 3}, 0, 3));
|
||||||
|
|
||||||
byte[] arr = new byte[10];
|
byte[] arr = new byte[10];
|
||||||
assertEquals(3, q.read(arr, true));
|
assertEquals(3, q.read(arr, true));
|
||||||
assertArrayEquals(new byte[] { 1, 2, 3 }, new byte[] { arr[0], arr[1], arr[2] });
|
assertArrayEquals(new byte[]{1, 2, 3}, new byte[]{arr[0], arr[1], arr[2]});
|
||||||
|
|
||||||
assertEquals(true, q.write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 0, 10));
|
assertEquals(true, q.write(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0, 10));
|
||||||
assertEquals(10, q.read(arr, true));
|
assertEquals(10, q.read(arr, true));
|
||||||
assertArrayEquals(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, arr);
|
assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testQueueWraparound() throws Exception {
|
public void testQueueWraparound() throws Exception {
|
||||||
ByteQueue q = new ByteQueue(10);
|
ByteQueue q = new ByteQueue(10);
|
||||||
|
|
||||||
byte[] origArray = new byte[] { 1, 2, 3, 4, 5, 6 };
|
byte[] origArray = new byte[]{1, 2, 3, 4, 5, 6};
|
||||||
byte[] readArray = new byte[origArray.length];
|
byte[] readArray = new byte[origArray.length];
|
||||||
for (int i = 0; i < 20; i++) {
|
for (int i = 0; i < 20; i++) {
|
||||||
q.write(origArray, 0, origArray.length);
|
q.write(origArray, 0, origArray.length);
|
||||||
@@ -43,7 +43,7 @@ public class ByteQueueTest extends TestCase {
|
|||||||
public void testWriteNotesClosing() throws Exception {
|
public void testWriteNotesClosing() throws Exception {
|
||||||
ByteQueue q = new ByteQueue(10);
|
ByteQueue q = new ByteQueue(10);
|
||||||
q.close();
|
q.close();
|
||||||
assertEquals(false, q.write(new byte[] { 1, 2, 3 }, 0, 3));
|
assertEquals(false, q.write(new byte[]{1, 2, 3}, 0, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadNonBlocking() throws Exception {
|
public void testReadNonBlocking() throws Exception {
|
||||||
|
|||||||
@@ -20,4 +20,13 @@ public class ControlSequenceIntroducerTest extends TerminalTestCase {
|
|||||||
withTerminalSized(3, 4).enterString("1\r\n2\r\n3\r\nhi\033[Sy").assertLinesAre("2 ", "3 ", "hi ", " y");
|
withTerminalSized(3, 4).enterString("1\r\n2\r\n3\r\nhi\033[Sy").assertLinesAre("2 ", "3 ", "hi ", " y");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** CSI Ps X Erase Ps Character(s) (default = 1) (ECH). */
|
||||||
|
public void testCsiX() {
|
||||||
|
// See https://code.google.com/p/chromium/issues/detail?id=212712 where test was extraced from.
|
||||||
|
withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[X").assertLinesAre("abcdefg ijkl ", " ");
|
||||||
|
withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[1X").assertLinesAre("abcdefg ijkl ", " ");
|
||||||
|
withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[2X").assertLinesAre("abcdefg jkl ", " ");
|
||||||
|
withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[20X").assertLinesAre("abcdefg ", " ");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
|||||||
assertLinesAre("ABCDE", "FGHIJ", "KLMNO", "PQRST", "UVWXY");
|
assertLinesAre("ABCDE", "FGHIJ", "KLMNO", "PQRST", "UVWXY");
|
||||||
for (int row = 0; row < 5; row++) {
|
for (int row = 0; row < 5; row++) {
|
||||||
for (int col = 0; col < 5; col++) {
|
for (int col = 0; col < 5; col++) {
|
||||||
int s = getStyleAt(row, col);
|
long s = getStyleAt(row, col);
|
||||||
Assert.assertEquals(col, TextStyle.decodeForeColor(s));
|
Assert.assertEquals(col, TextStyle.decodeForeColor(s));
|
||||||
Assert.assertEquals(row, TextStyle.decodeBackColor(s));
|
Assert.assertEquals(row, TextStyle.decodeBackColor(s));
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
|||||||
assertLinesAre("KLMNO", "PQRST", "UVWXY", " ", " ");
|
assertLinesAre("KLMNO", "PQRST", "UVWXY", " ", " ");
|
||||||
for (int row = 0; row < 3; row++) {
|
for (int row = 0; row < 3; row++) {
|
||||||
for (int col = 0; col < 5; col++) {
|
for (int col = 0; col < 5; col++) {
|
||||||
int s = getStyleAt(row, col);
|
long s = getStyleAt(row, col);
|
||||||
Assert.assertEquals(col, TextStyle.decodeForeColor(s));
|
Assert.assertEquals(col, TextStyle.decodeForeColor(s));
|
||||||
Assert.assertEquals(row + 2, TextStyle.decodeBackColor(s));
|
Assert.assertEquals(row + 2, TextStyle.decodeBackColor(s));
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
|||||||
for (int col = 0; col < 5; col++) {
|
for (int col = 0; col < 5; col++) {
|
||||||
int wantedForeground = (row == 1 || row == 2) ? 98 : col;
|
int wantedForeground = (row == 1 || row == 2) ? 98 : col;
|
||||||
int wantedBackground = (row == 1 || row == 2) ? 99 : (row == 0 ? 2 : row);
|
int wantedBackground = (row == 1 || row == 2) ? 99 : (row == 0 ? 2 : row);
|
||||||
int s = getStyleAt(row, col);
|
long s = getStyleAt(row, col);
|
||||||
Assert.assertEquals(wantedForeground, TextStyle.decodeForeColor(s));
|
Assert.assertEquals(wantedForeground, TextStyle.decodeForeColor(s));
|
||||||
Assert.assertEquals(wantedBackground, TextStyle.decodeBackColor(s));
|
Assert.assertEquals(wantedBackground, TextStyle.decodeBackColor(s));
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,12 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testHorizontalTabColorsBackground() {
|
/**
|
||||||
|
* See comments on horizontal tab handling in TerminalEmulator.java.
|
||||||
|
*
|
||||||
|
* We do not want to color already written cells when tabbing over them.
|
||||||
|
*/
|
||||||
|
public void DISABLED_testHorizontalTabColorsBackground() {
|
||||||
withTerminalSized(10, 3).enterString("\033[48;5;15m").enterString("\t");
|
withTerminalSized(10, 3).enterString("\033[48;5;15m").enterString("\t");
|
||||||
assertCursorAt(0, 8);
|
assertCursorAt(0, 8);
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
@@ -172,4 +177,54 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test interactions between the cursor overflow bit and various escape sequences.
|
||||||
|
* <p/>
|
||||||
|
* Adapted from hterm:
|
||||||
|
* https://chromium.googlesource.com/chromiumos/platform/assets/+/2337afa5c063127d5ce40ec7fec9b602d096df86%5E%21/#F2
|
||||||
|
*/
|
||||||
|
public void testClearingOfAutowrap() {
|
||||||
|
// Fill a row with the last hyphen wrong, then run a command that
|
||||||
|
// modifies the screen, then add a hyphen. The wrap bit should be
|
||||||
|
// cleared, so the extra hyphen can fix the row.
|
||||||
|
withTerminalSized(15, 6);
|
||||||
|
|
||||||
|
enterString("----- 1 ----X");
|
||||||
|
enterString("\033[K-"); // EL
|
||||||
|
|
||||||
|
enterString("----- 2 ----X");
|
||||||
|
enterString("\033[J-"); // ED
|
||||||
|
|
||||||
|
enterString("----- 3 ----X");
|
||||||
|
enterString("\033[@-"); // ICH
|
||||||
|
|
||||||
|
enterString("----- 4 ----X");
|
||||||
|
enterString("\033[P-"); // DCH
|
||||||
|
|
||||||
|
enterString("----- 5 ----X");
|
||||||
|
enterString("\033[X-"); // ECH
|
||||||
|
|
||||||
|
// DL will delete the entire line but clear the wrap bit, so we
|
||||||
|
// expect a hyphen at the end and nothing else.
|
||||||
|
enterString("XXXXXXXXXXXXXXX");
|
||||||
|
enterString("\033[M-"); // DL
|
||||||
|
|
||||||
|
assertLinesAre(
|
||||||
|
"----- 1 -----",
|
||||||
|
"----- 2 -----",
|
||||||
|
"----- 3 -----",
|
||||||
|
"----- 4 -----",
|
||||||
|
"----- 5 -----",
|
||||||
|
" -");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBackspaceAcrossWrappedLines() {
|
||||||
|
// Backspace should not go to previous line if not auto-wrapped:
|
||||||
|
withTerminalSized(3, 3).enterString("hi\r\n\b\byou").assertLinesAre("hi ", "you", " ");
|
||||||
|
// Backspace should go to previous line if auto-wrapped:
|
||||||
|
withTerminalSized(3, 3).enterString("hi y").assertLinesAre("hi ", "y ", " ").enterString("\b\b#").assertLinesAre("hi#", "y ", " ");
|
||||||
|
// Initial backspace should do nothing:
|
||||||
|
withTerminalSized(3, 3).enterString("\b\b\b\bhi").assertLinesAre("hi ", " ", " ");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ package com.termux.terminal;
|
|||||||
* <pre>
|
* <pre>
|
||||||
* "CSI ? Pm h", DEC Private Mode Set (DECSET)
|
* "CSI ? Pm h", DEC Private Mode Set (DECSET)
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
* <p/>
|
||||||
* and
|
* and
|
||||||
*
|
* <p/>
|
||||||
* <pre>
|
* <pre>
|
||||||
* "CSI ? Pm l", DEC Private Mode Reset (DECRST)
|
* "CSI ? Pm l", DEC Private Mode Reset (DECRST)
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
* <p/>
|
||||||
* controls various aspects of the terminal
|
* controls various aspects of the terminal
|
||||||
*/
|
*/
|
||||||
public class DecSetTest extends TerminalTestCase {
|
public class DecSetTest extends TerminalTestCase {
|
||||||
@@ -28,6 +28,11 @@ public class DecSetTest extends TerminalTestCase {
|
|||||||
assertFalse(mTerminal.isShowingCursor());
|
assertFalse(mTerminal.isShowingCursor());
|
||||||
mTerminal.reset();
|
mTerminal.reset();
|
||||||
assertTrue("Resetting the terminal should show the cursor", mTerminal.isShowingCursor());
|
assertTrue("Resetting the terminal should show the cursor", mTerminal.isShowingCursor());
|
||||||
|
|
||||||
|
enterString("\033[?25l");
|
||||||
|
assertFalse(mTerminal.isShowingCursor());
|
||||||
|
enterString("\033c"); // RIS resetting should reveal cursor.
|
||||||
|
assertTrue(mTerminal.isShowingCursor());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** DECSET 2004, controls bracketed paste mode. */
|
/** DECSET 2004, controls bracketed paste mode. */
|
||||||
@@ -59,4 +64,15 @@ public class DecSetTest extends TerminalTestCase {
|
|||||||
assertEquals("Terminal reset() should disable bracketed paste mode", "a", mOutput.getOutputAndClear());
|
assertEquals("Terminal reset() should disable bracketed paste mode", "a", mOutput.getOutputAndClear());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** DECSET 7, DECAWM, controls wraparound mode. */
|
||||||
|
public void testWrapAroundMode() {
|
||||||
|
// Default with wraparound:
|
||||||
|
withTerminalSized(3, 3).enterString("abcd").assertLinesAre("abc", "d ", " ");
|
||||||
|
// With wraparound disabled:
|
||||||
|
withTerminalSized(3, 3).enterString("\033[?7labcd").assertLinesAre("abd", " ", " ");
|
||||||
|
enterString("efg").assertLinesAre("abg", " ", " ");
|
||||||
|
// Re-enabling wraparound:
|
||||||
|
enterString("\033[?7hhij").assertLinesAre("abh", "ij ", " ");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
public class KeyHandlerTest extends TestCase {
|
public class KeyHandlerTest extends TestCase {
|
||||||
@@ -110,6 +111,10 @@ public class KeyHandlerTest extends TestCase {
|
|||||||
// Backspace.
|
// Backspace.
|
||||||
assertKeysEquals("\u007f", KeyHandler.getCode(KeyEvent.KEYCODE_DEL, 0, false, false));
|
assertKeysEquals("\u007f", KeyHandler.getCode(KeyEvent.KEYCODE_DEL, 0, false, false));
|
||||||
|
|
||||||
|
// Space.
|
||||||
|
assertNull(KeyHandler.getCode(KeyEvent.KEYCODE_SPACE, 0, false, false));
|
||||||
|
assertKeysEquals("\u0000", KeyHandler.getCode(KeyEvent.KEYCODE_SPACE, KeyHandler.KEYMOD_CTRL, false, false));
|
||||||
|
|
||||||
// Back tab.
|
// Back tab.
|
||||||
assertKeysEquals("\033[Z", KeyHandler.getCode(KeyEvent.KEYCODE_TAB, KeyHandler.KEYMOD_SHIFT, false, false));
|
assertKeysEquals("\033[Z", KeyHandler.getCode(KeyEvent.KEYCODE_TAB, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||||
|
|
||||||
@@ -136,10 +141,10 @@ public class KeyHandlerTest extends TestCase {
|
|||||||
assertKeysEquals("\033[1;6D", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_LEFT, mod, false, false));
|
assertKeysEquals("\033[1;6D", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_LEFT, mod, false, false));
|
||||||
|
|
||||||
// Home/end keys:
|
// Home/end keys:
|
||||||
assertKeysEquals("\033[H", KeyHandler.getCode(KeyEvent.KEYCODE_HOME, 0, false, false));
|
assertKeysEquals("\033[H", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_HOME, 0, false, false));
|
||||||
assertKeysEquals("\033[F", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_END, 0, false, false));
|
assertKeysEquals("\033[F", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_END, 0, false, false));
|
||||||
// ... shifted:
|
// ... shifted:
|
||||||
assertKeysEquals("\033[1;2H", KeyHandler.getCode(KeyEvent.KEYCODE_HOME, KeyHandler.KEYMOD_SHIFT, false, false));
|
assertKeysEquals("\033[1;2H", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_HOME, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||||
assertKeysEquals("\033[1;2F", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_END, KeyHandler.KEYMOD_SHIFT, false, false));
|
assertKeysEquals("\033[1;2F", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_END, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||||
|
|
||||||
// Function keys F1-F12:
|
// Function keys F1-F12:
|
||||||
@@ -168,5 +173,19 @@ public class KeyHandlerTest extends TestCase {
|
|||||||
assertKeysEquals("\033[21;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F10, KeyHandler.KEYMOD_SHIFT, false, false));
|
assertKeysEquals("\033[21;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F10, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||||
assertKeysEquals("\033[23;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F11, KeyHandler.KEYMOD_SHIFT, false, false));
|
assertKeysEquals("\033[23;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F11, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||||
assertKeysEquals("\033[24;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F12, KeyHandler.KEYMOD_SHIFT, false, false));
|
assertKeysEquals("\033[24;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F12, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||||
|
|
||||||
|
assertKeysEquals("0", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_0, 0, false, false));
|
||||||
|
assertKeysEquals("1", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_1, 0, false, false));
|
||||||
|
assertKeysEquals("2", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_2, 0, false, false));
|
||||||
|
assertKeysEquals("3", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_3, 0, false, false));
|
||||||
|
assertKeysEquals("4", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_4, 0, false, false));
|
||||||
|
assertKeysEquals("5", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_5, 0, false, false));
|
||||||
|
assertKeysEquals("6", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_6, 0, false, false));
|
||||||
|
assertKeysEquals("7", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_7, 0, false, false));
|
||||||
|
assertKeysEquals("8", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_8, 0, false, false));
|
||||||
|
assertKeysEquals("9", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_9, 0, false, false));
|
||||||
|
assertKeysEquals(",", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_COMMA, 0, false, false));
|
||||||
|
assertKeysEquals(".", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_DOT, 0, false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import android.util.Base64;
|
|
||||||
|
|
||||||
/** "ESC ]" is the Operating System Command. */
|
/** "ESC ]" is the Operating System Command. */
|
||||||
public class OperatingSystemControlTest extends TerminalTestCase {
|
public class OperatingSystemControlTest extends TerminalTestCase {
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ public class OperatingSystemControlTest extends TerminalTestCase {
|
|||||||
withTerminalSized(10, 10);
|
withTerminalSized(10, 10);
|
||||||
enterString("\033]0;Hello, world\007");
|
enterString("\033]0;Hello, world\007");
|
||||||
assertEquals("Hello, world", mTerminal.getTitle());
|
assertEquals("Hello, world", mTerminal.getTitle());
|
||||||
expectedTitleChanges.add(new ChangedTitle((String) null, "Hello, world"));
|
expectedTitleChanges.add(new ChangedTitle(null, "Hello, world"));
|
||||||
assertEquals(expectedTitleChanges, mOutput.titleChanges);
|
assertEquals(expectedTitleChanges, mOutput.titleChanges);
|
||||||
|
|
||||||
enterString("\033]0;Goodbye, world\007");
|
enterString("\033]0;Goodbye, world\007");
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ public class ResizeTest extends TerminalTestCase {
|
|||||||
enterString("\033[2J");
|
enterString("\033[2J");
|
||||||
for (int r = 0; r < rows; r++) {
|
for (int r = 0; r < rows; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
for (int c = 0; c < cols; c++) {
|
||||||
int style = getStyleAt(r, c);
|
long style = getStyleAt(r, c);
|
||||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||||
assertEquals(129, TextStyle.decodeBackColor(style));
|
assertEquals(129, TextStyle.decodeBackColor(style));
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ public class ResizeTest extends TerminalTestCase {
|
|||||||
// After resize, screen should still be same color:
|
// After resize, screen should still be same color:
|
||||||
for (int r = 0; r < rows - 2; r++) {
|
for (int r = 0; r < rows - 2; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
for (int c = 0; c < cols; c++) {
|
||||||
int style = getStyleAt(r, c);
|
long style = getStyleAt(r, c);
|
||||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||||
assertEquals(129, TextStyle.decodeBackColor(style));
|
assertEquals(129, TextStyle.decodeBackColor(style));
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ public class ResizeTest extends TerminalTestCase {
|
|||||||
resize(cols, rows);
|
resize(cols, rows);
|
||||||
for (int r = 0; r < rows; r++) {
|
for (int r = 0; r < rows; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
for (int c = 0; c < cols; c++) {
|
||||||
int style = getStyleAt(r, c);
|
long style = getStyleAt(r, c);
|
||||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||||
assertEquals("wrong at row=" + r, r >= 3 ? 200 : 129, TextStyle.decodeBackColor(style));
|
assertEquals("wrong at row=" + r, r >= 3 ? 200 : 129, TextStyle.decodeBackColor(style));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
public class ScreenBufferTest extends TerminalTest {
|
public class ScreenBufferTest extends TerminalTestCase {
|
||||||
|
|
||||||
public void testBasics() {
|
public void testBasics() {
|
||||||
TerminalBuffer screen = new TerminalBuffer(5, 3, 3);
|
TerminalBuffer screen = new TerminalBuffer(5, 3, 3);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.termux.terminal;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* ${CSI}${top};${bottom}r" - set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM).
|
* ${CSI}${top};${bottom}r" - set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM).
|
||||||
*
|
* <p/>
|
||||||
* "DECSTBM moves the cursor to column 1, line 1 of the page" (http://www.vt100.net/docs/vt510-rm/DECSTBM).
|
* "DECSTBM moves the cursor to column 1, line 1 of the page" (http://www.vt100.net/docs/vt510-rm/DECSTBM).
|
||||||
*/
|
*/
|
||||||
public class ScrollRegionTest extends TerminalTestCase {
|
public class ScrollRegionTest extends TerminalTestCase {
|
||||||
@@ -94,4 +94,8 @@ public class ScrollRegionTest extends TerminalTestCase {
|
|||||||
withTerminalSized(3, 3).enterString("\033[?69h\033[0;2sABCD\0339").assertLinesAre("B ", "D ", " ");
|
withTerminalSized(3, 3).enterString("\033[?69h\033[0;2sABCD\0339").assertLinesAre("B ", "D ", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testScrollDownWithScrollRegion() {
|
||||||
|
withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 ");
|
||||||
|
enterString("\033[3r").enterString("\033[2T").assertLinesAre("1 ", "2 ", " ", " ", "3 ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
public class TerminalRowTest extends TestCase {
|
public class TerminalRowTest extends TestCase {
|
||||||
|
|
||||||
/** The properties of these code points are validated in {@link #testStaticConstants()}. */
|
/** The properties of these code points are validated in {@link #testStaticConstants()}. */
|
||||||
@@ -96,7 +96,7 @@ public class TerminalRowTest extends TestCase {
|
|||||||
assertEquals(80, row.getSpaceUsed());
|
assertEquals(80, row.getSpaceUsed());
|
||||||
assertColumnCharIndicesStartsWith(0, 1, 2, 3);
|
assertColumnCharIndicesStartsWith(0, 1, 2, 3);
|
||||||
|
|
||||||
char[] someChars = new char[] { 'a', 'c', 'e', '4', '5', '6', '7', '8' };
|
char[] someChars = new char[]{'a', 'c', 'e', '4', '5', '6', '7', '8'};
|
||||||
|
|
||||||
char[] rawLine = new char[80];
|
char[] rawLine = new char[80];
|
||||||
Arrays.fill(rawLine, ' ');
|
Arrays.fill(rawLine, ' ');
|
||||||
@@ -373,13 +373,13 @@ public class TerminalRowTest extends TestCase {
|
|||||||
assertEquals(0, WcWidth.width(0x009F));
|
assertEquals(0, WcWidth.width(0x009F));
|
||||||
assertEquals(1, Character.charCount(0x009F));
|
assertEquals(1, Character.charCount(0x009F));
|
||||||
|
|
||||||
int[] points = new int[] { 0xC2541, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD, 'B', 0x009B, 0x61C9, 'Z' };
|
int[] points = new int[]{0xC2541, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD, 'B', 0x009B, 0x61C9, 'Z'};
|
||||||
// int[] expected = new int[] { TerminalEmulator.UNICODE_REPLACEMENT_CHAR, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD,
|
// int[] expected = new int[] { TerminalEmulator.UNICODE_REPLACEMENT_CHAR, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD,
|
||||||
// 'B', 0x009B, 0x61C9, 'Z' };
|
// 'B', 0x009B, 0x61C9, 'Z' };
|
||||||
int currentColumn = 0;
|
int currentColumn = 0;
|
||||||
for (int i = 0; i < points.length; i++) {
|
for (int point : points) {
|
||||||
row.setChar(currentColumn, points[i], 0);
|
row.setChar(currentColumn, point, 0);
|
||||||
currentColumn += WcWidth.width(points[i]);
|
currentColumn += WcWidth.width(point);
|
||||||
}
|
}
|
||||||
// assertLineStartsWith(points);
|
// assertLineStartsWith(points);
|
||||||
// assertEquals(Character.highSurrogate(0xC2541), line.mText[0]);
|
// assertEquals(Character.highSurrogate(0xC2541), line.mText[0]);
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
assertEnteringStringGivesResponse("\033[6n", "\033[2;1R");
|
assertEnteringStringGivesResponse("\033[6n", "\033[2;1R");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Test the cursor shape changes using DECSCUSR. */
|
||||||
public void testSetCursorStyle() throws Exception {
|
public void testSetCursorStyle() throws Exception {
|
||||||
withTerminalSized(5, 5);
|
withTerminalSized(5, 5);
|
||||||
assertEquals(TerminalEmulator.CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
assertEquals(TerminalEmulator.CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||||
@@ -146,7 +147,6 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
enterString("\033[38;5;119m");
|
enterString("\033[38;5;119m");
|
||||||
assertEquals(119, mTerminal.mForeColor);
|
assertEquals(119, mTerminal.mForeColor);
|
||||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||||
|
|
||||||
enterString("\033[48;5;129m");
|
enterString("\033[48;5;129m");
|
||||||
assertEquals(119, mTerminal.mForeColor);
|
assertEquals(119, mTerminal.mForeColor);
|
||||||
assertEquals(129, mTerminal.mBackColor);
|
assertEquals(129, mTerminal.mBackColor);
|
||||||
@@ -160,6 +160,30 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
enterString("\033[38;5;178;48;5;179;m");
|
enterString("\033[38;5;178;48;5;179;m");
|
||||||
assertEquals(178, mTerminal.mForeColor);
|
assertEquals(178, mTerminal.mForeColor);
|
||||||
assertEquals(179, mTerminal.mBackColor);
|
assertEquals(179, mTerminal.mBackColor);
|
||||||
|
|
||||||
|
// 24 bit colors:
|
||||||
|
enterString(("\033[0m")); // Reset fg and bg colors.
|
||||||
|
enterString("\033[38;2;255;127;2m");
|
||||||
|
int expectedForeground = 0xff000000 | (255 << 16) | (127 << 8) | 2;
|
||||||
|
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||||
|
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||||
|
enterString("\033[48;2;1;2;254m");
|
||||||
|
int expectedBackground = 0xff000000 | (1 << 16) | (2 << 8) | 254;
|
||||||
|
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||||
|
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||||
|
|
||||||
|
// 24 bit colors, set fg and bg at once:
|
||||||
|
enterString(("\033[0m")); // Reset fg and bg colors.
|
||||||
|
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||||
|
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||||
|
enterString("\033[38;2;255;127;2;48;2;1;2;254m");
|
||||||
|
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||||
|
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||||
|
|
||||||
|
// 24 bit colors, invalid input:
|
||||||
|
enterString("\033[38;2;300;127;2;48;2;1;300;254m");
|
||||||
|
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||||
|
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBackgroundColorErase() {
|
public void testBackgroundColorErase() {
|
||||||
@@ -168,7 +192,7 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
withTerminalSized(cols, rows);
|
withTerminalSized(cols, rows);
|
||||||
for (int r = 0; r < rows; r++) {
|
for (int r = 0; r < rows; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
for (int c = 0; c < cols; c++) {
|
||||||
int style = getStyleAt(r, c);
|
long style = getStyleAt(r, c);
|
||||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.decodeForeColor(style));
|
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.decodeForeColor(style));
|
||||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, TextStyle.decodeBackColor(style));
|
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, TextStyle.decodeBackColor(style));
|
||||||
}
|
}
|
||||||
@@ -181,7 +205,7 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
enterString("\033[2J");
|
enterString("\033[2J");
|
||||||
for (int r = 0; r < rows; r++) {
|
for (int r = 0; r < rows; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
for (int c = 0; c < cols; c++) {
|
||||||
int style = getStyleAt(r, c);
|
long style = getStyleAt(r, c);
|
||||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||||
assertEquals(129, TextStyle.decodeBackColor(style));
|
assertEquals(129, TextStyle.decodeBackColor(style));
|
||||||
}
|
}
|
||||||
@@ -192,7 +216,7 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
enterString("\033[2L");
|
enterString("\033[2L");
|
||||||
for (int r = 0; r < rows; r++) {
|
for (int r = 0; r < rows; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
for (int c = 0; c < cols; c++) {
|
||||||
int style = getStyleAt(r, c);
|
long style = getStyleAt(r, c);
|
||||||
assertEquals((r == 0 || r == 1) ? 139 : 129, TextStyle.decodeBackColor(style));
|
assertEquals((r == 0 || r == 1) ? 139 : 129, TextStyle.decodeBackColor(style));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,4 +281,9 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
withTerminalSized(3, 3).enterString("abc\r ").assertLinesAre(" bc", " ", " ").assertCursorAt(0, 1);
|
withTerminalSized(3, 3).enterString("abc\r ").assertLinesAre(" bc", " ", " ").assertCursorAt(0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testTab() {
|
||||||
|
withTerminalSized(11, 2).enterString("01234567890\r\tXX").assertLinesAre("01234567XX0", " ");
|
||||||
|
withTerminalSized(11, 2).enterString("01234567890\033[44m\r\tXX").assertLinesAre("01234567XX0", " ");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
|
import junit.framework.AssertionFailedError;
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -9,19 +12,14 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import junit.framework.AssertionFailedError;
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import com.termux.terminal.TerminalEmulator;
|
|
||||||
import com.termux.terminal.TerminalOutput;
|
|
||||||
|
|
||||||
public abstract class TerminalTestCase extends TestCase {
|
public abstract class TerminalTestCase extends TestCase {
|
||||||
|
|
||||||
public static class MockTerminalOutput extends TerminalOutput {
|
public static class MockTerminalOutput extends TerminalOutput {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
public final List<ChangedTitle> titleChanges = new ArrayList<>();
|
public final List<ChangedTitle> titleChanges = new ArrayList<>();
|
||||||
public final List<String> clipboardPuts = new ArrayList<>();
|
public final List<String> clipboardPuts = new ArrayList<>();
|
||||||
public int bellsRung = 0;
|
public int bellsRung = 0;
|
||||||
|
public int colorsChanged = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] data, int offset, int count) {
|
public void write(byte[] data, int offset, int count) {
|
||||||
@@ -52,12 +50,17 @@ public abstract class TerminalTestCase extends TestCase {
|
|||||||
public void onBell() {
|
public void onBell() {
|
||||||
bellsRung++;
|
bellsRung++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onColorsChanged() {
|
||||||
|
colorsChanged++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TerminalEmulator mTerminal;
|
public TerminalEmulator mTerminal;
|
||||||
public MockTerminalOutput mOutput;
|
public MockTerminalOutput mOutput;
|
||||||
|
|
||||||
public static class ChangedTitle {
|
public static final class ChangedTitle {
|
||||||
final String oldTitle;
|
final String oldTitle;
|
||||||
final String newTitle;
|
final String newTitle;
|
||||||
|
|
||||||
@@ -68,6 +71,7 @@ public abstract class TerminalTestCase extends TestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof ChangedTitle)) return false;
|
||||||
ChangedTitle other = (ChangedTitle) o;
|
ChangedTitle other = (ChangedTitle) o;
|
||||||
return Objects.equals(oldTitle, other.oldTitle) && Objects.equals(newTitle, other.newTitle);
|
return Objects.equals(oldTitle, other.oldTitle) && Objects.equals(newTitle, other.newTitle);
|
||||||
}
|
}
|
||||||
@@ -115,8 +119,8 @@ public abstract class TerminalTestCase extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LineWrapper {
|
private static final class LineWrapper {
|
||||||
TerminalRow mLine;
|
final TerminalRow mLine;
|
||||||
|
|
||||||
public LineWrapper(TerminalRow line) {
|
public LineWrapper(TerminalRow line) {
|
||||||
mLine = line;
|
mLine = line;
|
||||||
@@ -129,7 +133,7 @@ public abstract class TerminalTestCase extends TestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
return ((LineWrapper) o).mLine == mLine;
|
return o instanceof LineWrapper && ((LineWrapper) o).mLine == mLine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +240,7 @@ public abstract class TerminalTestCase extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** For testing only. Encoded style according to {@link TextStyle}. */
|
/** For testing only. Encoded style according to {@link TextStyle}. */
|
||||||
public int getStyleAt(int externalRow, int column) {
|
public long getStyleAt(int externalRow, int column) {
|
||||||
return mTerminal.getScreen().getStyleAt(externalRow, column);
|
return mTerminal.getScreen().getStyleAt(externalRow, column);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +296,7 @@ public abstract class TerminalTestCase extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void assertForegroundColorAt(int externalRow, int column, int color) {
|
public void assertForegroundColorAt(int externalRow, int column, int color) {
|
||||||
int style = mTerminal.getScreen().mLines[mTerminal.getScreen().externalToInternalRow(externalRow)].getStyle(column);
|
long style = mTerminal.getScreen().mLines[mTerminal.getScreen().externalToInternalRow(externalRow)].getStyle(column);
|
||||||
assertEquals(color, TextStyle.decodeForeColor(style));
|
assertEquals(color, TextStyle.decodeForeColor(style));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ import junit.framework.TestCase;
|
|||||||
|
|
||||||
public class TextStyleTest extends TestCase {
|
public class TextStyleTest extends TestCase {
|
||||||
|
|
||||||
private static final int[] ALL_EFFECTS = new int[] { 0, TextStyle.CHARACTER_ATTRIBUTE_BOLD, TextStyle.CHARACTER_ATTRIBUTE_ITALIC,
|
private static final int[] ALL_EFFECTS = new int[]{0, TextStyle.CHARACTER_ATTRIBUTE_BOLD, TextStyle.CHARACTER_ATTRIBUTE_ITALIC,
|
||||||
TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.CHARACTER_ATTRIBUTE_BLINK, TextStyle.CHARACTER_ATTRIBUTE_INVERSE,
|
TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.CHARACTER_ATTRIBUTE_BLINK, TextStyle.CHARACTER_ATTRIBUTE_INVERSE,
|
||||||
TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE, TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH, TextStyle.CHARACTER_ATTRIBUTE_PROTECTED,
|
TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE, TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH, TextStyle.CHARACTER_ATTRIBUTE_PROTECTED,
|
||||||
TextStyle.CHARACTER_ATTRIBUTE_DIM };
|
TextStyle.CHARACTER_ATTRIBUTE_DIM};
|
||||||
|
|
||||||
public void testEncodingSingle() {
|
public void testEncodingSingle() {
|
||||||
for (int fx : ALL_EFFECTS) {
|
for (int fx : ALL_EFFECTS) {
|
||||||
for (int fg = 0; fg < TextStyle.NUM_INDEXED_COLORS; fg++) {
|
for (int fg = 0; fg < TextStyle.NUM_INDEXED_COLORS; fg++) {
|
||||||
for (int bg = 0; bg < TextStyle.NUM_INDEXED_COLORS; bg++) {
|
for (int bg = 0; bg < TextStyle.NUM_INDEXED_COLORS; bg++) {
|
||||||
int encoded = TextStyle.encode(fg, bg, fx);
|
long encoded = TextStyle.encode(fg, bg, fx);
|
||||||
assertEquals(fg, TextStyle.decodeForeColor(encoded));
|
assertEquals(fg, TextStyle.decodeForeColor(encoded));
|
||||||
assertEquals(bg, TextStyle.decodeBackColor(encoded));
|
assertEquals(bg, TextStyle.decodeBackColor(encoded));
|
||||||
assertEquals(fx, TextStyle.decodeEffect(encoded));
|
assertEquals(fx, TextStyle.decodeEffect(encoded));
|
||||||
@@ -22,6 +22,22 @@ public class TextStyleTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testEncoding24Bit() {
|
||||||
|
int[] values = {255, 240, 127, 1, 0};
|
||||||
|
for (int red : values) {
|
||||||
|
for (int green : values) {
|
||||||
|
for (int blue : values) {
|
||||||
|
int argb = 0xFF000000 | (red << 16) | (green << 8) | blue;
|
||||||
|
long encoded = TextStyle.encode(argb, 0, 0);
|
||||||
|
assertEquals(argb, TextStyle.decodeForeColor(encoded));
|
||||||
|
encoded = TextStyle.encode(0, argb, 0);
|
||||||
|
assertEquals(argb, TextStyle.decodeBackColor(encoded));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void testEncodingCombinations() {
|
public void testEncodingCombinations() {
|
||||||
for (int f1 : ALL_EFFECTS) {
|
for (int f1 : ALL_EFFECTS) {
|
||||||
for (int f2 : ALL_EFFECTS) {
|
for (int f2 : ALL_EFFECTS) {
|
||||||
@@ -32,13 +48,13 @@ public class TextStyleTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testEncodingStrikeThrough() {
|
public void testEncodingStrikeThrough() {
|
||||||
int encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
||||||
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH);
|
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH);
|
||||||
assertTrue((TextStyle.decodeEffect(encoded) | TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0);
|
assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEncodingProtected() {
|
public void testEncodingProtected() {
|
||||||
int encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
||||||
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH);
|
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH);
|
||||||
assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0);
|
assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0);
|
||||||
encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
||||||
|
|||||||
@@ -10,14 +10,55 @@ public class UnicodeInputTest extends TerminalTestCase {
|
|||||||
// continue with valid successor bytes (see Table 3-7), it must not consume the successor bytes as part of the ill-formed
|
// continue with valid successor bytes (see Table 3-7), it must not consume the successor bytes as part of the ill-formed
|
||||||
// subsequence whenever those successor bytes themselves constitute part of a well-formed UTF-8 code unit subsequence."
|
// subsequence whenever those successor bytes themselves constitute part of a well-formed UTF-8 code unit subsequence."
|
||||||
withTerminalSized(5, 5);
|
withTerminalSized(5, 5);
|
||||||
mTerminal.append(new byte[] { (byte) 0b11101111, (byte) 'a' }, 2);
|
mTerminal.append(new byte[]{(byte) 0b11101111, (byte) 'a'}, 2);
|
||||||
assertLineIs(0, ((char) TerminalEmulator.UNICODE_REPLACEMENT_CHAR) + "a ");
|
assertLineIs(0, ((char) TerminalEmulator.UNICODE_REPLACEMENT_CHAR) + "a ");
|
||||||
|
|
||||||
|
// https://code.google.com/p/chromium/issues/detail?id=212704
|
||||||
|
byte[] input = new byte[]{
|
||||||
|
(byte) 0x61, (byte) 0xF1,
|
||||||
|
(byte) 0x80, (byte) 0x80,
|
||||||
|
(byte) 0xe1, (byte) 0x80,
|
||||||
|
(byte) 0xc2, (byte) 0x62,
|
||||||
|
(byte) 0x80, (byte) 0x63,
|
||||||
|
(byte) 0x80, (byte) 0xbf,
|
||||||
|
(byte) 0x64
|
||||||
|
};
|
||||||
|
withTerminalSized(10, 2);
|
||||||
|
mTerminal.append(input, input.length);
|
||||||
|
assertLinesAre("a\uFFFD\uFFFD\uFFFDb\uFFFDc\uFFFD\uFFFDd", " ");
|
||||||
|
|
||||||
|
// Surrogate pairs.
|
||||||
|
withTerminalSized(5, 2);
|
||||||
|
input = new byte[]{
|
||||||
|
(byte) 0xed, (byte) 0xa0,
|
||||||
|
(byte) 0x80, (byte) 0xed,
|
||||||
|
(byte) 0xad, (byte) 0xbf,
|
||||||
|
(byte) 0xed, (byte) 0xae,
|
||||||
|
(byte) 0x80, (byte) 0xed,
|
||||||
|
(byte) 0xbf, (byte) 0xbf
|
||||||
|
};
|
||||||
|
mTerminal.append(input, input.length);
|
||||||
|
assertLinesAre("\uFFFD\uFFFD\uFFFD\uFFFD ", " ");
|
||||||
|
|
||||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=746900: "with this patch 0xe0 0x80 is decoded as two U+FFFDs,
|
||||||
|
// but 0xe0 0xa0 is decoded as a single U+FFFD, and this is correct according to the "Best Practices", but IE
|
||||||
|
// and Chrome (Version 22.0.1229.94) decode both of them as two U+FFFDs. Opera 12.11 decodes both of them as
|
||||||
|
// one U+FFFD".
|
||||||
|
withTerminalSized(5, 2);
|
||||||
|
input = new byte[]{(byte) 0xe0, (byte) 0xa0, ' '};
|
||||||
|
mTerminal.append(input, input.length);
|
||||||
|
assertLinesAre("\uFFFD ", " ");
|
||||||
|
|
||||||
|
// withTerminalSized(5, 2);
|
||||||
|
// input = new byte[]{(byte) 0xe0, (byte) 0x80, 'a'};
|
||||||
|
// mTerminal.append(input, input.length);
|
||||||
|
// assertLinesAre("\uFFFD\uFFFDa ", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testUnassignedCodePoint() throws UnsupportedEncodingException {
|
public void testUnassignedCodePoint() throws UnsupportedEncodingException {
|
||||||
withTerminalSized(3, 3);
|
withTerminalSized(3, 3);
|
||||||
// UTF-8 for U+C2541, an unassigned code point:
|
// UTF-8 for U+C2541, an unassigned code point:
|
||||||
byte[] b = new byte[] { (byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81 };
|
byte[] b = new byte[]{(byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81};
|
||||||
mTerminal.append(b, b.length);
|
mTerminal.append(b, b.length);
|
||||||
enterString("Y");
|
enterString("Y");
|
||||||
assertEquals(1, Character.charCount(TerminalEmulator.UNICODE_REPLACEMENT_CHAR));
|
assertEquals(1, Character.charCount(TerminalEmulator.UNICODE_REPLACEMENT_CHAR));
|
||||||
@@ -26,10 +67,10 @@ public class UnicodeInputTest extends TerminalTestCase {
|
|||||||
|
|
||||||
public void testStuff() {
|
public void testStuff() {
|
||||||
withTerminalSized(80, 24);
|
withTerminalSized(80, 24);
|
||||||
byte[] b = new byte[] { (byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81, (byte) 0x61, (byte) 0x38, (byte) 0xe7, (byte) 0x8f,
|
byte[] b = new byte[]{(byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81, (byte) 0x61, (byte) 0x38, (byte) 0xe7, (byte) 0x8f,
|
||||||
(byte) 0xae, (byte) 0xc2, (byte) 0x9f, (byte) 0xe8, (byte) 0xa0, (byte) 0x9f, (byte) 0xe8, (byte) 0x8c, (byte) 0xa4,
|
(byte) 0xae, (byte) 0xc2, (byte) 0x9f, (byte) 0xe8, (byte) 0xa0, (byte) 0x9f, (byte) 0xe8, (byte) 0x8c, (byte) 0xa4,
|
||||||
(byte) 0xed, (byte) 0x93, (byte) 0x89, (byte) 0xef, (byte) 0xbf, (byte) 0xbd, (byte) 0x42, (byte) 0xc2, (byte) 0x9b,
|
(byte) 0xed, (byte) 0x93, (byte) 0x89, (byte) 0xef, (byte) 0xbf, (byte) 0xbd, (byte) 0x42, (byte) 0xc2, (byte) 0x9b,
|
||||||
(byte) 0xe6, (byte) 0x87, (byte) 0x89, (byte) 0x5a };
|
(byte) 0xe6, (byte) 0x87, (byte) 0x89, (byte) 0x5a};
|
||||||
mTerminal.append(b, b.length);
|
mTerminal.append(b, b.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +121,16 @@ public class UnicodeInputTest extends TerminalTestCase {
|
|||||||
public void testOverlongUtf8Encoding() throws Exception {
|
public void testOverlongUtf8Encoding() throws Exception {
|
||||||
// U+0020 should be encoded as 0x20, 0xc0 0xa0 is an overlong encoding
|
// U+0020 should be encoded as 0x20, 0xc0 0xa0 is an overlong encoding
|
||||||
// so should be replaced with the replacement char U+FFFD.
|
// so should be replaced with the replacement char U+FFFD.
|
||||||
withTerminalSized(5, 5).mTerminal.append(new byte[] { (byte) 0xc0, (byte) 0xa0, 'Y' }, 3);
|
withTerminalSized(5, 5).mTerminal.append(new byte[]{(byte) 0xc0, (byte) 0xa0, 'Y'}, 3);
|
||||||
assertLineIs(0, "\uFFFDY ");
|
assertLineIs(0, "\uFFFDY ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testWideCharacterWithoutWrapping() throws Exception {
|
||||||
|
// With wraparound disabled. The behaviour when a wide character is output with cursor in
|
||||||
|
// the last column when autowrap is disabled is not obvious, but we expect the wide
|
||||||
|
// character to be ignored here.
|
||||||
|
withTerminalSized(3, 3).enterString("\033[?7l").enterString("枝枝枝").assertLinesAre("枝 ", " ", " ");
|
||||||
|
enterString("a枝").assertLinesAre("枝a", " ", " ");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ public class WcWidthTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testSomeWidthOne() {
|
||||||
|
assertWidthIs(1, 'å');
|
||||||
|
assertWidthIs(1, 'ä');
|
||||||
|
assertWidthIs(1, 'ö');
|
||||||
|
}
|
||||||
|
|
||||||
public void testSomeWide() {
|
public void testSomeWide() {
|
||||||
assertWidthIs(2, 'A');
|
assertWidthIs(2, 'A');
|
||||||
assertWidthIs(2, 'B');
|
assertWidthIs(2, 'B');
|
||||||
@@ -41,8 +47,17 @@ public class WcWidthTest extends TestCase {
|
|||||||
assertWidthIs(0, 0x0308);
|
assertWidthIs(0, 0x0308);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testWordJoiner() {
|
||||||
|
// https://en.wikipedia.org/wiki/Word_joiner
|
||||||
|
// The word joiner (WJ) is a code point in Unicode used to separate words when using scripts
|
||||||
|
// that do not use explicit spacing. It is encoded since Unicode version 3.2
|
||||||
|
// (released in 2002) as U+2060 WORD JOINER (HTML ⁠).
|
||||||
|
// The word joiner does not produce any space, and prohibits a line break at its position.
|
||||||
|
assertWidthIs(0, 0x2060);
|
||||||
|
}
|
||||||
|
|
||||||
public void testWatch() {
|
public void testWatch() {
|
||||||
assertWidthIs(1, 0x231a);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSofthyphen() {
|
public void testSofthyphen() {
|
||||||
@@ -56,7 +71,13 @@ public class WcWidthTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testHangul() {
|
public void testHangul() {
|
||||||
assertWidthIs(2, 0x11A3);
|
assertWidthIs(1, 0x11A3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEmojis() {
|
||||||
|
assertWidthIs(2, 0x1F428); // KOALA.
|
||||||
|
assertWidthIs(2, 0x231a); // WATCH.
|
||||||
|
assertWidthIs(2, 0x1F643); // UPSIDE-DOWN FACE (Unicode 8).
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Script to build jni libraries while waiting for the new gradle build system:
|
|
||||||
# http://tools.android.com/tech-docs/new-build-system/gradle-experimental#TOC-Ndk-Integration
|
|
||||||
|
|
||||||
set -e -u
|
|
||||||
|
|
||||||
PROJECTDIR=`mktemp -d`
|
|
||||||
JNIDIR=$PROJECTDIR/jni
|
|
||||||
LIBSDIR=$PROJECTDIR/libs
|
|
||||||
|
|
||||||
mkdir $JNIDIR
|
|
||||||
cp app/src/main/jni/* $JNIDIR/
|
|
||||||
|
|
||||||
ndk-build NDK_PROJECT_PATH=$PROJECTDIR
|
|
||||||
cp -Rf $LIBSDIR/* app/src/main/jniLibs/
|
|
||||||
|
|
||||||
rm -Rf $PROJECTDIR
|
|
||||||
@@ -5,10 +5,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:1.3.1'
|
classpath 'com.android.tools.build:gradle:2.1.3'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
|
||||||
// in the individual module build.gradle files
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
# Project-wide Gradle settings.
|
## Project-wide Gradle settings.
|
||||||
|
#
|
||||||
# IDE (e.g. Android Studio) users:
|
|
||||||
# Gradle settings configured through the IDE *will override*
|
|
||||||
# any settings specified in this file.
|
|
||||||
|
|
||||||
# For more details on how to configure your build environment visit
|
# For more details on how to configure your build environment visit
|
||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
#
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||||
|
#
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
|
#Fri May 13 01:11:09 CEST 2016
|
||||||
|
org.gradle.jvmargs=-Xmx2048M
|
||||||
|
android.useDeprecatedNdk=true
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#Thu Oct 22 22:50:58 CEST 2015
|
#Sat Jul 23 17:08:29 CEST 2016
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip
|
||||||
|
|||||||
46
gradlew
vendored
46
gradlew
vendored
@@ -6,12 +6,30 @@
|
|||||||
##
|
##
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Attempt to set APP_HOME
|
||||||
DEFAULT_JVM_OPTS=""
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
|
|
||||||
@@ -30,6 +48,7 @@ die ( ) {
|
|||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
case "`uname`" in
|
case "`uname`" in
|
||||||
CYGWIN* )
|
CYGWIN* )
|
||||||
cygwin=true
|
cygwin=true
|
||||||
@@ -40,26 +59,11 @@ case "`uname`" in
|
|||||||
MINGW* )
|
MINGW* )
|
||||||
msys=true
|
msys=true
|
||||||
;;
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
PRG="$0"
|
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
|
||||||
ls=`ls -ld "$PRG"`
|
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
|
||||||
PRG="$link"
|
|
||||||
else
|
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >&-
|
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >&-
|
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -85,7 +89,7 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
if [ $? -eq 0 ] ; then
|
if [ $? -eq 0 ] ; then
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
|||||||
90
gradlew.bat
vendored
90
gradlew.bat
vendored
@@ -1,90 +0,0 @@
|
|||||||
@if "%DEBUG%" == "" @echo off
|
|
||||||
@rem ##########################################################################
|
|
||||||
@rem
|
|
||||||
@rem Gradle startup script for Windows
|
|
||||||
@rem
|
|
||||||
@rem ##########################################################################
|
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS=
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
|
||||||
set APP_BASE_NAME=%~n0
|
|
||||||
set APP_HOME=%DIRNAME%
|
|
||||||
|
|
||||||
@rem Find java.exe
|
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:init
|
|
||||||
@rem Get command-line arguments, handling Windowz variants
|
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
|
||||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
|
||||||
|
|
||||||
:win9xME_args
|
|
||||||
@rem Slurp the command line arguments.
|
|
||||||
set CMD_LINE_ARGS=
|
|
||||||
set _SKIP=2
|
|
||||||
|
|
||||||
:win9xME_args_slurp
|
|
||||||
if "x%~1" == "x" goto execute
|
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%*
|
|
||||||
goto execute
|
|
||||||
|
|
||||||
:4NT_args
|
|
||||||
@rem Get arguments from the 4NT Shell from JP Software
|
|
||||||
set CMD_LINE_ARGS=%$
|
|
||||||
|
|
||||||
:execute
|
|
||||||
@rem Setup the command line
|
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
|
||||||
|
|
||||||
:end
|
|
||||||
@rem End local scope for the variables with windows NT shell
|
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
|
||||||
exit /b 1
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
BIN
travis.keystore
BIN
travis.keystore
Binary file not shown.
Reference in New Issue
Block a user