mirror of
https://github.com/termux/termux-app
synced 2024-06-28 03:57:06 +00:00
Reformat code project-wide (getting rid of tabs)
This commit is contained in:
parent
d72fd579ee
commit
2db6923bc4
231
.idea/codeStyleSettings.xml
Normal file
231
.idea/codeStyleSettings.xml
Normal file
|
@ -0,0 +1,231 @@
|
|||
<?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_KEEP_LINE_BREAKS" value="false" />
|
||||
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
|
||||
<option name="XML_SPACE_INSIDE_EMPTY_TAG" 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>
|
|
@ -21,7 +21,7 @@ public final class BackgroundJob {
|
|||
public BackgroundJob(File cwd, File fileToExecute, String[] args) throws IOException {
|
||||
String[] env = buildEnvironment(false, cwd.getAbsolutePath());
|
||||
|
||||
String[] progArray = new String[args.length+1];
|
||||
String[] progArray = new String[args.length + 1];
|
||||
|
||||
mProcess = Runtime.getRuntime().exec(progArray, env, cwd);
|
||||
|
||||
|
@ -81,32 +81,32 @@ public final class BackgroundJob {
|
|||
}
|
||||
|
||||
public String[] buildEnvironment(boolean failSafe, String cwd) {
|
||||
new File(TermuxService.HOME_PATH).mkdirs();
|
||||
new File(TermuxService.HOME_PATH).mkdirs();
|
||||
|
||||
if (cwd == null) cwd = TermuxService.HOME_PATH;
|
||||
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;
|
||||
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 };
|
||||
}
|
||||
return new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,52 +13,52 @@ import android.widget.TextView;
|
|||
|
||||
public final class DialogUtils {
|
||||
|
||||
public interface TextSetListener {
|
||||
void onTextSet(String text);
|
||||
}
|
||||
public interface TextSetListener {
|
||||
void onTextSet(String text);
|
||||
}
|
||||
|
||||
public static void textInput(Activity activity, int titleText, String initialText,
|
||||
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);
|
||||
input.setSingleLine();
|
||||
if (initialText != null) {
|
||||
input.setText(initialText);
|
||||
Selection.setSelection(input.getText(), initialText.length());
|
||||
}
|
||||
final EditText input = new EditText(activity);
|
||||
input.setSingleLine();
|
||||
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;
|
||||
}
|
||||
});
|
||||
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());
|
||||
// https://www.google.com/design/spec/components/dialogs.html#dialogs-specs
|
||||
int paddingTopAndSides = Math.round(16 * dipInPixels);
|
||||
int paddingBottom = Math.round(24 * dipInPixels);
|
||||
float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, activity.getResources().getDisplayMetrics());
|
||||
// https://www.google.com/design/spec/components/dialogs.html#dialogs-specs
|
||||
int paddingTopAndSides = Math.round(16 * dipInPixels);
|
||||
int paddingBottom = Math.round(24 * dipInPixels);
|
||||
|
||||
LinearLayout layout = new LinearLayout(activity);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
|
||||
layout.setPadding(paddingTopAndSides, paddingTopAndSides, paddingTopAndSides, paddingBottom);
|
||||
layout.addView(input);
|
||||
LinearLayout layout = new LinearLayout(activity);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
|
||||
layout.setPadding(paddingTopAndSides, paddingTopAndSides, paddingTopAndSides, paddingBottom);
|
||||
layout.addView(input);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity)
|
||||
.setTitle(titleText).setView(layout)
|
||||
.setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface d, int whichButton) {
|
||||
onPositive.onTextSet(input.getText().toString());
|
||||
}
|
||||
});
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity)
|
||||
.setTitle(titleText).setView(layout)
|
||||
.setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface d, int whichButton) {
|
||||
onPositive.onTextSet(input.getText().toString());
|
||||
}
|
||||
});
|
||||
|
||||
if (onNeutral != null) {
|
||||
builder.setNeutralButton(neutralButtonText, new DialogInterface.OnClickListener() {
|
||||
|
@ -82,9 +82,9 @@ public final class DialogUtils {
|
|||
|
||||
if (onDismiss != null) builder.setOnDismissListener(onDismiss);
|
||||
|
||||
dialogHolder[0] = builder.create();
|
||||
dialogHolder[0] = builder.create();
|
||||
dialogHolder[0].setCanceledOnTouchOutside(false);
|
||||
dialogHolder[0].show();
|
||||
}
|
||||
dialogHolder[0].show();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,14 +31,29 @@ public final class ExtraKeysView extends GridLayout {
|
|||
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;
|
||||
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) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,61 +15,61 @@ import android.widget.RelativeLayout;
|
|||
/** Basic embedded browser for viewing help pages. */
|
||||
public final class TermuxHelpActivity extends Activity {
|
||||
|
||||
private WebView mWebView;
|
||||
private WebView mWebView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@Override
|
||||
protected void onCreate(Bundle 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);
|
||||
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);
|
||||
WebSettings settings = mWebView.getSettings();
|
||||
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
|
||||
settings.setAppCacheEnabled(false);
|
||||
setContentView(progressLayout);
|
||||
mWebView.clearCache(true);
|
||||
mWebView = new WebView(this);
|
||||
WebSettings settings = mWebView.getSettings();
|
||||
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
|
||||
settings.setAppCacheEnabled(false);
|
||||
setContentView(progressLayout);
|
||||
mWebView.clearCache(true);
|
||||
|
||||
mWebView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url.startsWith("https://termux.com")) {
|
||||
// Inline help.
|
||||
setContentView(progressLayout);
|
||||
return false;
|
||||
}
|
||||
mWebView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url.startsWith("https://termux.com")) {
|
||||
// Inline help.
|
||||
setContentView(progressLayout);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// Android TV does not have a system browser.
|
||||
setContentView(progressLayout);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// Android TV does not have a system browser.
|
||||
setContentView(progressLayout);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
setContentView(mWebView);
|
||||
}
|
||||
});
|
||||
mWebView.loadUrl("https://termux.com/help.html");
|
||||
}
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
setContentView(mWebView);
|
||||
}
|
||||
});
|
||||
mWebView.loadUrl("https://termux.com/help.html");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mWebView.canGoBack()) {
|
||||
mWebView.goBack();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mWebView.canGoBack()) {
|
||||
mWebView.goBack();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,152 +31,155 @@ import java.util.zip.ZipInputStream;
|
|||
|
||||
/**
|
||||
* 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
|
||||
* broken $PREFIX folder below.
|
||||
*
|
||||
* <p/>
|
||||
* (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.
|
||||
*
|
||||
* <p/>
|
||||
* (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
|
||||
* 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.
|
||||
*
|
||||
* <p/>
|
||||
* (5.2) For every other zip entry, extract it into $STAGING_PREFIX and set execute permissions if necessary.
|
||||
*/
|
||||
final class TermuxInstaller {
|
||||
|
||||
/** Performs setup if necessary. */
|
||||
static void setupIfNeeded(final Activity activity, final Runnable whenDone) {
|
||||
// Termux can only be run as the primary user (device owner) since only that
|
||||
// account has the expected file system paths. Verify that:
|
||||
android.os.UserManager um = (android.os.UserManager) activity.getSystemService(Context.USER_SERVICE);
|
||||
boolean isPrimaryUser = um.getSerialNumberForUser(android.os.Process.myUserHandle()) == 0;
|
||||
if (!isPrimaryUser) {
|
||||
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_not_primary_user_message)
|
||||
.setOnDismissListener(new OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
System.exit(0);
|
||||
}
|
||||
}).setPositiveButton(android.R.string.ok, null).show();
|
||||
return;
|
||||
}
|
||||
/** Performs setup if necessary. */
|
||||
static void setupIfNeeded(final Activity activity, final Runnable whenDone) {
|
||||
// Termux can only be run as the primary user (device owner) since only that
|
||||
// account has the expected file system paths. Verify that:
|
||||
android.os.UserManager um = (android.os.UserManager) activity.getSystemService(Context.USER_SERVICE);
|
||||
boolean isPrimaryUser = um.getSerialNumberForUser(android.os.Process.myUserHandle()) == 0;
|
||||
if (!isPrimaryUser) {
|
||||
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_not_primary_user_message)
|
||||
.setOnDismissListener(new OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
System.exit(0);
|
||||
}
|
||||
}).setPositiveButton(android.R.string.ok, null).show();
|
||||
return;
|
||||
}
|
||||
|
||||
final File PREFIX_FILE = new File(TermuxService.PREFIX_PATH);
|
||||
if (PREFIX_FILE.isDirectory()) {
|
||||
whenDone.run();
|
||||
return;
|
||||
}
|
||||
final File PREFIX_FILE = new File(TermuxService.PREFIX_PATH);
|
||||
if (PREFIX_FILE.isDirectory()) {
|
||||
whenDone.run();
|
||||
return;
|
||||
}
|
||||
|
||||
final ProgressDialog progress = ProgressDialog.show(activity, null, activity.getString(R.string.bootstrap_installer_body), true, false);
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
final String STAGING_PREFIX_PATH = TermuxService.FILES_PATH + "/usr-staging";
|
||||
final File STAGING_PREFIX_FILE = new File(STAGING_PREFIX_PATH);
|
||||
final ProgressDialog progress = ProgressDialog.show(activity, null, activity.getString(R.string.bootstrap_installer_body), true, false);
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
final String STAGING_PREFIX_PATH = TermuxService.FILES_PATH + "/usr-staging";
|
||||
final File STAGING_PREFIX_FILE = new File(STAGING_PREFIX_PATH);
|
||||
|
||||
if (STAGING_PREFIX_FILE.exists()) {
|
||||
deleteFolder(STAGING_PREFIX_FILE);
|
||||
}
|
||||
if (STAGING_PREFIX_FILE.exists()) {
|
||||
deleteFolder(STAGING_PREFIX_FILE);
|
||||
}
|
||||
|
||||
final byte[] buffer = new byte[8096];
|
||||
final List<Pair<String, String>> symlinks = new ArrayList<>(50);
|
||||
final byte[] buffer = new byte[8096];
|
||||
final List<Pair<String, String>> symlinks = new ArrayList<>(50);
|
||||
|
||||
final URL zipUrl = determineZipUrl();
|
||||
try (ZipInputStream zipInput = new ZipInputStream(zipUrl.openStream())) {
|
||||
ZipEntry zipEntry;
|
||||
while ((zipEntry = zipInput.getNextEntry()) != null) {
|
||||
if (zipEntry.getName().equals("SYMLINKS.txt")) {
|
||||
BufferedReader symlinksReader = new BufferedReader(new InputStreamReader(zipInput));
|
||||
String line;
|
||||
while ((line = symlinksReader.readLine()) != null) {
|
||||
String[] parts = line.split("←");
|
||||
if (parts.length != 2) throw new RuntimeException("Malformed symlink line: " + line);
|
||||
String oldPath = parts[0];
|
||||
String newPath = STAGING_PREFIX_PATH + "/" + parts[1];
|
||||
symlinks.add(Pair.create(oldPath, newPath));
|
||||
}
|
||||
} else {
|
||||
String zipEntryName = zipEntry.getName();
|
||||
File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName);
|
||||
if (zipEntry.isDirectory()) {
|
||||
if (!targetFile.mkdirs()) throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath());
|
||||
} else {
|
||||
try (FileOutputStream outStream = new FileOutputStream(targetFile)) {
|
||||
int readBytes;
|
||||
while ((readBytes = zipInput.read(buffer)) != -1)
|
||||
outStream.write(buffer, 0, readBytes);
|
||||
}
|
||||
if (zipEntryName.startsWith("bin/") || zipEntryName.startsWith("libexec") || zipEntryName.startsWith("lib/apt/methods")) {
|
||||
//noinspection OctalInteger
|
||||
Os.chmod(targetFile.getAbsolutePath(), 0700);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final URL zipUrl = determineZipUrl();
|
||||
try (ZipInputStream zipInput = new ZipInputStream(zipUrl.openStream())) {
|
||||
ZipEntry zipEntry;
|
||||
while ((zipEntry = zipInput.getNextEntry()) != null) {
|
||||
if (zipEntry.getName().equals("SYMLINKS.txt")) {
|
||||
BufferedReader symlinksReader = new BufferedReader(new InputStreamReader(zipInput));
|
||||
String line;
|
||||
while ((line = symlinksReader.readLine()) != null) {
|
||||
String[] parts = line.split("←");
|
||||
if (parts.length != 2)
|
||||
throw new RuntimeException("Malformed symlink line: " + line);
|
||||
String oldPath = parts[0];
|
||||
String newPath = STAGING_PREFIX_PATH + "/" + parts[1];
|
||||
symlinks.add(Pair.create(oldPath, newPath));
|
||||
}
|
||||
} else {
|
||||
String zipEntryName = zipEntry.getName();
|
||||
File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName);
|
||||
if (zipEntry.isDirectory()) {
|
||||
if (!targetFile.mkdirs())
|
||||
throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath());
|
||||
} else {
|
||||
try (FileOutputStream outStream = new FileOutputStream(targetFile)) {
|
||||
int readBytes;
|
||||
while ((readBytes = zipInput.read(buffer)) != -1)
|
||||
outStream.write(buffer, 0, readBytes);
|
||||
}
|
||||
if (zipEntryName.startsWith("bin/") || zipEntryName.startsWith("libexec") || zipEntryName.startsWith("lib/apt/methods")) {
|
||||
//noinspection OctalInteger
|
||||
Os.chmod(targetFile.getAbsolutePath(), 0700);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (symlinks.isEmpty()) throw new RuntimeException("No SYMLINKS.txt encountered");
|
||||
for (Pair<String, String> symlink : symlinks) {
|
||||
Os.symlink(symlink.first, symlink.second);
|
||||
}
|
||||
if (symlinks.isEmpty())
|
||||
throw new RuntimeException("No SYMLINKS.txt encountered");
|
||||
for (Pair<String, String> symlink : symlinks) {
|
||||
Os.symlink(symlink.first, symlink.second);
|
||||
}
|
||||
|
||||
if (!STAGING_PREFIX_FILE.renameTo(PREFIX_FILE)) {
|
||||
throw new RuntimeException("Unable to rename staging folder");
|
||||
}
|
||||
if (!STAGING_PREFIX_FILE.renameTo(PREFIX_FILE)) {
|
||||
throw new RuntimeException("Unable to rename staging folder");
|
||||
}
|
||||
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
whenDone.run();
|
||||
}
|
||||
});
|
||||
} catch (final Exception e) {
|
||||
Log.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e);
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
|
||||
.setNegativeButton(R.string.bootstrap_error_abort, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
activity.finish();
|
||||
}
|
||||
}).setPositiveButton(R.string.bootstrap_error_try_again, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
TermuxInstaller.setupIfNeeded(activity, whenDone);
|
||||
}
|
||||
}).show();
|
||||
} catch (WindowManager.BadTokenException e) {
|
||||
// Activity already dismissed - ignore.
|
||||
}
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
progress.dismiss();
|
||||
} catch (RuntimeException e) {
|
||||
// Activity already dismissed - ignore.
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
whenDone.run();
|
||||
}
|
||||
});
|
||||
} catch (final Exception e) {
|
||||
Log.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e);
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
|
||||
.setNegativeButton(R.string.bootstrap_error_abort, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
activity.finish();
|
||||
}
|
||||
}).setPositiveButton(R.string.bootstrap_error_try_again, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
TermuxInstaller.setupIfNeeded(activity, whenDone);
|
||||
}
|
||||
}).show();
|
||||
} catch (WindowManager.BadTokenException e) {
|
||||
// Activity already dismissed - ignore.
|
||||
}
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
progress.dismiss();
|
||||
} catch (RuntimeException e) {
|
||||
// Activity already dismissed - ignore.
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
/** Get bootstrap zip url for this systems cpu architecture. */
|
||||
static URL determineZipUrl() throws MalformedURLException {
|
||||
|
@ -201,64 +204,64 @@ final class TermuxInstaller {
|
|||
return new URL("https://termux.net/bootstrap/bootstrap-" + termuxArch + ".zip");
|
||||
}
|
||||
|
||||
/** Delete a folder and all its content or throw. */
|
||||
static void deleteFolder(File fileOrDirectory) {
|
||||
File[] children = fileOrDirectory.listFiles();
|
||||
if (children != null) {
|
||||
for (File child : children) {
|
||||
deleteFolder(child);
|
||||
}
|
||||
}
|
||||
if (!fileOrDirectory.delete()) {
|
||||
throw new RuntimeException("Unable to delete " + (fileOrDirectory.isDirectory() ? "directory " : "file ") + fileOrDirectory.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
/** Delete a folder and all its content or throw. */
|
||||
static void deleteFolder(File fileOrDirectory) {
|
||||
File[] children = fileOrDirectory.listFiles();
|
||||
if (children != null) {
|
||||
for (File child : children) {
|
||||
deleteFolder(child);
|
||||
}
|
||||
}
|
||||
if (!fileOrDirectory.delete()) {
|
||||
throw new RuntimeException("Unable to delete " + (fileOrDirectory.isDirectory() ? "directory " : "file ") + fileOrDirectory.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
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.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;
|
||||
}
|
||||
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 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 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 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 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 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());
|
||||
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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -111,7 +111,8 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
|||
mActivity.changeFontSize(false);
|
||||
} else if (unicodeChar >= '1' && unicodeChar <= '9') {
|
||||
int num = unicodeChar - '1';
|
||||
if (service.getSessions().size() > num) mActivity.switchToSession(service.getSessions().get(num));
|
||||
if (service.getSessions().size() > num)
|
||||
mActivity.switchToSession(service.getSessions().get(num));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -235,7 +236,7 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
|||
session.writeCodePoint(altDown, resultingCodePoint);
|
||||
}
|
||||
return true;
|
||||
} else if (ctrlDown) {
|
||||
} else if (ctrlDown) {
|
||||
List<TermuxPreferences.KeyboardShortcut> shortcuts = mActivity.mSettings.shortcuts;
|
||||
if (!shortcuts.isEmpty()) {
|
||||
for (int i = shortcuts.size() - 1; i >= 0; i--) {
|
||||
|
|
|
@ -20,66 +20,67 @@ import java.util.Properties;
|
|||
|
||||
final class TermuxPreferences {
|
||||
|
||||
@IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface AsciiBellBehaviour {}
|
||||
@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;
|
||||
static final int BELL_VIBRATE = 1;
|
||||
static final int BELL_BEEP = 2;
|
||||
static final int BELL_IGNORE = 3;
|
||||
|
||||
private final int MIN_FONTSIZE;
|
||||
private static final int MAX_FONTSIZE = 256;
|
||||
private final int MIN_FONTSIZE;
|
||||
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 CURRENT_SESSION_KEY = "current_session";
|
||||
private static final String SHOW_WELCOME_DIALOG_KEY = "intro_dialog";
|
||||
private static final String FONTSIZE_KEY = "fontsize";
|
||||
private static final String CURRENT_SESSION_KEY = "current_session";
|
||||
private static final String SHOW_WELCOME_DIALOG_KEY = "intro_dialog";
|
||||
|
||||
private boolean mFullScreen;
|
||||
private int mFontSize;
|
||||
private boolean mFullScreen;
|
||||
private int mFontSize;
|
||||
|
||||
@AsciiBellBehaviour
|
||||
int mBellBehaviour = BELL_VIBRATE;
|
||||
@AsciiBellBehaviour
|
||||
int mBellBehaviour = BELL_VIBRATE;
|
||||
|
||||
boolean mBackIsEscape;
|
||||
boolean mShowExtraKeys;
|
||||
|
||||
TermuxPreferences(Context context) {
|
||||
reloadFromProperties(context);
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
TermuxPreferences(Context context) {
|
||||
reloadFromProperties(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());
|
||||
|
||||
// This is a bit arbitrary and sub-optimal. We want to give a sensible default for minimum font size
|
||||
// to prevent invisible text due to zoom be mistake:
|
||||
MIN_FONTSIZE = (int) (4f * dipInPixels);
|
||||
// This is a bit arbitrary and sub-optimal. We want to give a sensible default for minimum font size
|
||||
// to prevent invisible text due to zoom be mistake:
|
||||
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
|
||||
int defaultFontSize = Math.round(12 * dipInPixels);
|
||||
// Make it divisible by 2 since that is the minimal adjustment step:
|
||||
if (defaultFontSize % 2 == 1) defaultFontSize--;
|
||||
// http://www.google.com/design/spec/style/typography.html#typography-line-height
|
||||
int defaultFontSize = Math.round(12 * dipInPixels);
|
||||
// Make it divisible by 2 since that is the minimal adjustment step:
|
||||
if (defaultFontSize % 2 == 1) defaultFontSize--;
|
||||
|
||||
try {
|
||||
mFontSize = Integer.parseInt(prefs.getString(FONTSIZE_KEY, Integer.toString(defaultFontSize)));
|
||||
} catch (NumberFormatException | ClassCastException e) {
|
||||
mFontSize = defaultFontSize;
|
||||
}
|
||||
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
|
||||
}
|
||||
try {
|
||||
mFontSize = Integer.parseInt(prefs.getString(FONTSIZE_KEY, Integer.toString(defaultFontSize)));
|
||||
} catch (NumberFormatException | ClassCastException e) {
|
||||
mFontSize = defaultFontSize;
|
||||
}
|
||||
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
|
||||
}
|
||||
|
||||
boolean isFullScreen() {
|
||||
return mFullScreen;
|
||||
}
|
||||
boolean isFullScreen() {
|
||||
return mFullScreen;
|
||||
}
|
||||
|
||||
void setFullScreen(Context context, boolean newValue) {
|
||||
mFullScreen = newValue;
|
||||
void setFullScreen(Context context, boolean newValue) {
|
||||
mFullScreen = newValue;
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(FULLSCREEN_KEY, newValue).apply();
|
||||
}
|
||||
}
|
||||
|
||||
boolean isShowExtraKeys() {
|
||||
return mShowExtraKeys;
|
||||
|
@ -92,74 +93,75 @@ final class TermuxPreferences {
|
|||
}
|
||||
|
||||
int getFontSize() {
|
||||
return mFontSize;
|
||||
}
|
||||
return mFontSize;
|
||||
}
|
||||
|
||||
void changeFontSize(Context context, boolean increase) {
|
||||
mFontSize += (increase ? 1 : -1) * 2;
|
||||
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
|
||||
void changeFontSize(Context context, boolean increase) {
|
||||
mFontSize += (increase ? 1 : -1) * 2;
|
||||
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putString(FONTSIZE_KEY, Integer.toString(mFontSize)).apply();
|
||||
}
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putString(FONTSIZE_KEY, Integer.toString(mFontSize)).apply();
|
||||
}
|
||||
|
||||
static void storeCurrentSession(Context context, TerminalSession session) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).commit();
|
||||
}
|
||||
static void storeCurrentSession(Context context, TerminalSession session) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).commit();
|
||||
}
|
||||
|
||||
static TerminalSession getCurrentSession(TermuxActivity context) {
|
||||
String sessionHandle = PreferenceManager.getDefaultSharedPreferences(context).getString(TermuxPreferences.CURRENT_SESSION_KEY, "");
|
||||
for (int i = 0, len = context.mTermService.getSessions().size(); i < len; i++) {
|
||||
TerminalSession session = context.mTermService.getSessions().get(i);
|
||||
if (session.mHandle.equals(sessionHandle)) return session;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
static TerminalSession getCurrentSession(TermuxActivity context) {
|
||||
String sessionHandle = PreferenceManager.getDefaultSharedPreferences(context).getString(TermuxPreferences.CURRENT_SESSION_KEY, "");
|
||||
for (int i = 0, len = context.mTermService.getSessions().size(); i < len; i++) {
|
||||
TerminalSession session = context.mTermService.getSessions().get(i);
|
||||
if (session.mHandle.equals(sessionHandle)) return session;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isShowWelcomeDialog(Context context) {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SHOW_WELCOME_DIALOG_KEY, true);
|
||||
}
|
||||
public static boolean isShowWelcomeDialog(Context context) {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SHOW_WELCOME_DIALOG_KEY, true);
|
||||
}
|
||||
|
||||
public static void disableWelcomeDialog(Context context) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_WELCOME_DIALOG_KEY, false).apply();
|
||||
}
|
||||
public static void disableWelcomeDialog(Context context) {
|
||||
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");
|
||||
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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
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"));
|
||||
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);
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
|
|
|
@ -1,15 +1,5 @@
|
|||
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.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
|
@ -26,331 +16,342 @@ import android.os.PowerManager;
|
|||
import android.util.Log;
|
||||
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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* {@link #buildNotification()}.
|
||||
*/
|
||||
public final class TermuxService extends Service implements SessionChangedCallback {
|
||||
|
||||
/** Note that this is a symlink on the Android M preview. */
|
||||
@SuppressLint("SdCardPath")
|
||||
public static final String FILES_PATH = "/data/data/com.termux/files";
|
||||
public static final String PREFIX_PATH = FILES_PATH + "/usr";
|
||||
public static final String HOME_PATH = FILES_PATH + "/home";
|
||||
/** Note that this is a symlink on the Android M preview. */
|
||||
@SuppressLint("SdCardPath")
|
||||
public static final String FILES_PATH = "/data/data/com.termux/files";
|
||||
public static final String PREFIX_PATH = FILES_PATH + "/usr";
|
||||
public static final String HOME_PATH = FILES_PATH + "/home";
|
||||
|
||||
private static final int NOTIFICATION_ID = 1337;
|
||||
private static final int NOTIFICATION_ID = 1337;
|
||||
|
||||
/** Intent action to stop the service. */
|
||||
private static final String ACTION_STOP_SERVICE = "com.termux.service_stop";
|
||||
/** Intent action to toggle the wake lock, {@link #mWakeLock}, which this service may hold. */
|
||||
private static final String ACTION_LOCK_WAKE = "com.termux.service_toggle_wake_lock";
|
||||
/** 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";
|
||||
/** Intent action to launch a new terminal session. Executed from TermuxWidgetProvider. */
|
||||
public static final String ACTION_EXECUTE = "com.termux.service_execute";
|
||||
/** Intent action to stop the service. */
|
||||
private static final String ACTION_STOP_SERVICE = "com.termux.service_stop";
|
||||
/** Intent action to toggle the wake lock, {@link #mWakeLock}, which this service may hold. */
|
||||
private static final String ACTION_LOCK_WAKE = "com.termux.service_toggle_wake_lock";
|
||||
/** 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";
|
||||
/** Intent action to launch a new terminal session. Executed from TermuxWidgetProvider. */
|
||||
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. */
|
||||
class LocalBinder extends Binder {
|
||||
public final TermuxService service = TermuxService.this;
|
||||
}
|
||||
/** This service is only bound from inside the same process and never uses IPC. */
|
||||
class LocalBinder extends Binder {
|
||||
public final TermuxService service = TermuxService.this;
|
||||
}
|
||||
|
||||
private final IBinder mBinder = new LocalBinder();
|
||||
private final IBinder mBinder = new LocalBinder();
|
||||
|
||||
/**
|
||||
* The terminal sessions which this service manages.
|
||||
*
|
||||
* 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()} }.
|
||||
*/
|
||||
final List<TerminalSession> mTerminalSessions = new ArrayList<>();
|
||||
/**
|
||||
* 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
|
||||
* thread and followed by a call to {@link ArrayAdapter#notifyDataSetChanged()} }.
|
||||
*/
|
||||
final List<TerminalSession> mTerminalSessions = new ArrayList<>();
|
||||
|
||||
/** Note that the service may often outlive the activity, so need to clear this reference. */
|
||||
SessionChangedCallback mSessionChangeCallback;
|
||||
/** Note that the service may often outlive the activity, so need to clear this reference. */
|
||||
SessionChangedCallback mSessionChangeCallback;
|
||||
|
||||
private PowerManager.WakeLock mWakeLock;
|
||||
private WifiManager.WifiLock mWifiLock;
|
||||
private PowerManager.WakeLock mWakeLock;
|
||||
private WifiManager.WifiLock mWifiLock;
|
||||
|
||||
/** If the user has executed the {@link #ACTION_STOP_SERVICE} intent. */
|
||||
boolean mWantsToStop = false;
|
||||
/** If the user has executed the {@link #ACTION_STOP_SERVICE} intent. */
|
||||
boolean mWantsToStop = false;
|
||||
|
||||
@SuppressLint("Wakelock")
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
String action = intent.getAction();
|
||||
if (ACTION_STOP_SERVICE.equals(action)) {
|
||||
mWantsToStop = true;
|
||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||
mTerminalSessions.get(i).finishIfRunning();
|
||||
stopSelf();
|
||||
} else if (ACTION_LOCK_WAKE.equals(action)) {
|
||||
if (mWakeLock == null) {
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG);
|
||||
mWakeLock.acquire();
|
||||
} else {
|
||||
mWakeLock.release();
|
||||
mWakeLock = null;
|
||||
}
|
||||
updateNotification();
|
||||
} else if (ACTION_LOCK_WIFI.equals(action)) {
|
||||
if (mWifiLock == null) {
|
||||
WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
||||
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
|
||||
mWifiLock.acquire();
|
||||
} else {
|
||||
mWifiLock.release();
|
||||
mWifiLock = null;
|
||||
}
|
||||
updateNotification();
|
||||
} else if (ACTION_EXECUTE.equals(action)) {
|
||||
Uri executableUri = intent.getData();
|
||||
String executablePath = (executableUri == null ? null : executableUri.getPath());
|
||||
String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra(EXTRA_ARGUMENTS));
|
||||
String cwd = intent.getStringExtra(EXTRA_CURRENT_WORKING_DIRECTORY);
|
||||
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, false);
|
||||
@SuppressLint("Wakelock")
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
String action = intent.getAction();
|
||||
if (ACTION_STOP_SERVICE.equals(action)) {
|
||||
mWantsToStop = true;
|
||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||
mTerminalSessions.get(i).finishIfRunning();
|
||||
stopSelf();
|
||||
} else if (ACTION_LOCK_WAKE.equals(action)) {
|
||||
if (mWakeLock == null) {
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG);
|
||||
mWakeLock.acquire();
|
||||
} else {
|
||||
mWakeLock.release();
|
||||
mWakeLock = null;
|
||||
}
|
||||
updateNotification();
|
||||
} else if (ACTION_LOCK_WIFI.equals(action)) {
|
||||
if (mWifiLock == null) {
|
||||
WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
||||
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
|
||||
mWifiLock.acquire();
|
||||
} else {
|
||||
mWifiLock.release();
|
||||
mWifiLock = null;
|
||||
}
|
||||
updateNotification();
|
||||
} else if (ACTION_EXECUTE.equals(action)) {
|
||||
Uri executableUri = intent.getData();
|
||||
String executablePath = (executableUri == null ? null : executableUri.getPath());
|
||||
String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra(EXTRA_ARGUMENTS));
|
||||
String cwd = intent.getStringExtra(EXTRA_CURRENT_WORKING_DIRECTORY);
|
||||
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, false);
|
||||
|
||||
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
||||
if (executablePath != null) {
|
||||
int lastSlash = executablePath.lastIndexOf('/');
|
||||
String name = (lastSlash == -1) ? executablePath : executablePath.substring(lastSlash + 1);
|
||||
name = name.replace('-', ' ');
|
||||
newSession.mSessionName = name;
|
||||
}
|
||||
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
||||
if (executablePath != null) {
|
||||
int lastSlash = executablePath.lastIndexOf('/');
|
||||
String name = (lastSlash == -1) ? executablePath : executablePath.substring(lastSlash + 1);
|
||||
name = name.replace('-', ' ');
|
||||
newSession.mSessionName = name;
|
||||
}
|
||||
|
||||
// Make the newly created session the current one to be displayed:
|
||||
TermuxPreferences.storeCurrentSession(this, newSession);
|
||||
// Make the newly created session the current one to be displayed:
|
||||
TermuxPreferences.storeCurrentSession(this, newSession);
|
||||
|
||||
// Launch the main Termux app, which will now show to current session:
|
||||
startActivity(new Intent(this, TermuxActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
} else if (action != null) {
|
||||
Log.e(EmulatorDebug.LOG_TAG, "Unknown TermuxService action: '" + action + "'");
|
||||
}
|
||||
// Launch the main Termux app, which will now show to current session:
|
||||
startActivity(new Intent(this, TermuxActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
} else if (action != null) {
|
||||
Log.e(EmulatorDebug.LOG_TAG, "Unknown TermuxService action: '" + action + "'");
|
||||
}
|
||||
|
||||
// If this service really do get killed, there is no point restarting it automatically - let the user do on next
|
||||
// start of {@link Term):
|
||||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
// If this service really do get killed, there is no point restarting it automatically - let the user do on next
|
||||
// start of {@link Term):
|
||||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
startForeground(NOTIFICATION_ID, buildNotification());
|
||||
}
|
||||
@Override
|
||||
public void onCreate() {
|
||||
startForeground(NOTIFICATION_ID, buildNotification());
|
||||
}
|
||||
|
||||
/** Update the shown foreground service notification after making any changes that affect it. */
|
||||
private void updateNotification() {
|
||||
if (mWakeLock == null && mWifiLock == null && getSessions().isEmpty()) {
|
||||
// Exit if we are updating after the user disabled all locks with no sessions.
|
||||
stopSelf();
|
||||
} else {
|
||||
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, buildNotification());
|
||||
}
|
||||
}
|
||||
/** Update the shown foreground service notification after making any changes that affect it. */
|
||||
private void updateNotification() {
|
||||
if (mWakeLock == null && mWifiLock == null && getSessions().isEmpty()) {
|
||||
// Exit if we are updating after the user disabled all locks with no sessions.
|
||||
stopSelf();
|
||||
} else {
|
||||
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, buildNotification());
|
||||
}
|
||||
}
|
||||
|
||||
private Notification buildNotification() {
|
||||
Intent notifyIntent = new Intent(this, TermuxActivity.class);
|
||||
// PendingIntent#getActivity(): "Note that the activity will be started outside of the context of an existing
|
||||
// activity, so you must use the Intent.FLAG_ACTIVITY_NEW_TASK launch flag in the Intent":
|
||||
notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0);
|
||||
private Notification buildNotification() {
|
||||
Intent notifyIntent = new Intent(this, TermuxActivity.class);
|
||||
// PendingIntent#getActivity(): "Note that the activity will be started outside of the context of an existing
|
||||
// activity, so you must use the Intent.FLAG_ACTIVITY_NEW_TASK launch flag in the Intent":
|
||||
notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0);
|
||||
|
||||
int sessionCount = mTerminalSessions.size();
|
||||
String contentText = sessionCount + " terminal session" + (sessionCount == 1 ? "" : "s");
|
||||
int sessionCount = mTerminalSessions.size();
|
||||
String contentText = sessionCount + " terminal session" + (sessionCount == 1 ? "" : "s");
|
||||
|
||||
boolean wakeLockHeld = mWakeLock != null;
|
||||
boolean wifiLockHeld = mWifiLock != null;
|
||||
if (wakeLockHeld && wifiLockHeld) {
|
||||
contentText += " (wake&wifi lock held)";
|
||||
} else if (wakeLockHeld) {
|
||||
contentText += " (wake lock held)";
|
||||
} else if (wifiLockHeld) {
|
||||
contentText += " (wifi lock held)";
|
||||
}
|
||||
boolean wakeLockHeld = mWakeLock != null;
|
||||
boolean wifiLockHeld = mWifiLock != null;
|
||||
if (wakeLockHeld && wifiLockHeld) {
|
||||
contentText += " (wake&wifi lock held)";
|
||||
} else if (wakeLockHeld) {
|
||||
contentText += " (wake lock held)";
|
||||
} else if (wifiLockHeld) {
|
||||
contentText += " (wifi lock held)";
|
||||
}
|
||||
|
||||
Notification.Builder builder = new Notification.Builder(this);
|
||||
builder.setContentTitle(getText(R.string.application_name));
|
||||
builder.setContentText(contentText);
|
||||
builder.setSmallIcon(R.drawable.ic_service_notification);
|
||||
builder.setContentIntent(pendingIntent);
|
||||
builder.setOngoing(true);
|
||||
Notification.Builder builder = new Notification.Builder(this);
|
||||
builder.setContentTitle(getText(R.string.application_name));
|
||||
builder.setContentText(contentText);
|
||||
builder.setSmallIcon(R.drawable.ic_service_notification);
|
||||
builder.setContentIntent(pendingIntent);
|
||||
builder.setOngoing(true);
|
||||
|
||||
// If holding a wake or wifi lock consider the notification of high priority since it's using power,
|
||||
// otherwise use a minimal priority since this is just a background service notification:
|
||||
builder.setPriority((wakeLockHeld || wifiLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_MIN);
|
||||
// If holding a wake or wifi lock consider the notification of high priority since it's using power,
|
||||
// otherwise use a minimal priority since this is just a background service notification:
|
||||
builder.setPriority((wakeLockHeld || wifiLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_MIN);
|
||||
|
||||
// No need to show a timestamp:
|
||||
builder.setShowWhen(false);
|
||||
// No need to show a timestamp:
|
||||
builder.setShowWhen(false);
|
||||
|
||||
// Background color for small notification icon:
|
||||
builder.setColor(0xFF000000);
|
||||
// Background color for small notification icon:
|
||||
builder.setColor(0xFF000000);
|
||||
|
||||
Resources res = getResources();
|
||||
Intent exitIntent = new Intent(this, TermuxService.class).setAction(ACTION_STOP_SERVICE);
|
||||
builder.addAction(android.R.drawable.ic_delete, res.getString(R.string.notification_action_exit), PendingIntent.getService(this, 0, exitIntent, 0));
|
||||
Resources res = getResources();
|
||||
Intent exitIntent = new Intent(this, TermuxService.class).setAction(ACTION_STOP_SERVICE);
|
||||
builder.addAction(android.R.drawable.ic_delete, res.getString(R.string.notification_action_exit), PendingIntent.getService(this, 0, exitIntent, 0));
|
||||
|
||||
Intent toggleWakeLockIntent = new Intent(this, TermuxService.class).setAction(ACTION_LOCK_WAKE);
|
||||
builder.addAction(android.R.drawable.ic_lock_lock, res.getString(R.string.notification_action_wakelock),
|
||||
PendingIntent.getService(this, 0, toggleWakeLockIntent, 0));
|
||||
Intent toggleWakeLockIntent = new Intent(this, TermuxService.class).setAction(ACTION_LOCK_WAKE);
|
||||
builder.addAction(android.R.drawable.ic_lock_lock, res.getString(R.string.notification_action_wakelock),
|
||||
PendingIntent.getService(this, 0, toggleWakeLockIntent, 0));
|
||||
|
||||
Intent toggleWifiLockIntent = new Intent(this, TermuxService.class).setAction(ACTION_LOCK_WIFI);
|
||||
builder.addAction(android.R.drawable.ic_lock_lock, res.getString(R.string.notification_action_wifilock),
|
||||
PendingIntent.getService(this, 0, toggleWifiLockIntent, 0));
|
||||
Intent toggleWifiLockIntent = new Intent(this, TermuxService.class).setAction(ACTION_LOCK_WIFI);
|
||||
builder.addAction(android.R.drawable.ic_lock_lock, res.getString(R.string.notification_action_wifilock),
|
||||
PendingIntent.getService(this, 0, toggleWifiLockIntent, 0));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (mWakeLock != null) mWakeLock.release();
|
||||
if (mWifiLock != null) mWifiLock.release();
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (mWakeLock != null) mWakeLock.release();
|
||||
if (mWifiLock != null) mWifiLock.release();
|
||||
|
||||
stopForeground(true);
|
||||
stopForeground(true);
|
||||
|
||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||
mTerminalSessions.get(i).finishIfRunning();
|
||||
mTerminalSessions.clear();
|
||||
}
|
||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||
mTerminalSessions.get(i).finishIfRunning();
|
||||
mTerminalSessions.clear();
|
||||
}
|
||||
|
||||
public List<TerminalSession> getSessions() {
|
||||
return mTerminalSessions;
|
||||
}
|
||||
public List<TerminalSession> getSessions() {
|
||||
return mTerminalSessions;
|
||||
}
|
||||
|
||||
TerminalSession createTermSession(String executablePath, String[] arguments, String cwd, boolean failSafe) {
|
||||
new File(HOME_PATH).mkdirs();
|
||||
TerminalSession createTermSession(String executablePath, String[] arguments, String cwd, boolean failSafe) {
|
||||
new File(HOME_PATH).mkdirs();
|
||||
|
||||
if (cwd == null) cwd = HOME_PATH;
|
||||
if (cwd == null) cwd = HOME_PATH;
|
||||
|
||||
final String termEnv = "TERM=xterm-256color";
|
||||
final String homeEnv = "HOME=" + HOME_PATH;
|
||||
final String prefixEnv = "PREFIX=" + PREFIX_PATH;
|
||||
final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT");
|
||||
final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA");
|
||||
final String termEnv = "TERM=xterm-256color";
|
||||
final String homeEnv = "HOME=" + HOME_PATH;
|
||||
final String prefixEnv = "PREFIX=" + 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) {
|
||||
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");
|
||||
env = new String[] { termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv };
|
||||
} else {
|
||||
final String ps1Env = "PS1=$ ";
|
||||
final String ldEnv = "LD_LIBRARY_PATH=" + PREFIX_PATH + "/lib";
|
||||
final String langEnv = "LANG=en_US.UTF-8";
|
||||
final String pathEnv = "PATH=" + PREFIX_PATH + "/bin:" + PREFIX_PATH + "/bin/applets";
|
||||
final String pwdEnv = "PWD=" + cwd;
|
||||
env = new String[]{termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv};
|
||||
} else {
|
||||
final String ps1Env = "PS1=$ ";
|
||||
final String ldEnv = "LD_LIBRARY_PATH=" + PREFIX_PATH + "/lib";
|
||||
final String langEnv = "LANG=en_US.UTF-8";
|
||||
final String pathEnv = "PATH=" + PREFIX_PATH + "/bin:" + PREFIX_PATH + "/bin/applets";
|
||||
final String pwdEnv = "PWD=" + cwd;
|
||||
|
||||
env = new String[] { termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv };
|
||||
}
|
||||
env = new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv};
|
||||
}
|
||||
|
||||
String shellName;
|
||||
if (executablePath == null) {
|
||||
File shell = new File(HOME_PATH, ".termux/shell");
|
||||
if (shell.exists()) {
|
||||
try {
|
||||
File canonicalFile = shell.getCanonicalFile();
|
||||
if (canonicalFile.isFile() && canonicalFile.canExecute()) {
|
||||
executablePath = canonicalFile.getName().equals("busybox") ? (PREFIX_PATH + "/bin/ash") : canonicalFile.getAbsolutePath();
|
||||
} else {
|
||||
Log.w(EmulatorDebug.LOG_TAG, "$HOME/.termux/shell points to non-executable shell: " + canonicalFile.getAbsolutePath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(EmulatorDebug.LOG_TAG, "Error checking $HOME/.termux/shell", e);
|
||||
}
|
||||
}
|
||||
String shellName;
|
||||
if (executablePath == null) {
|
||||
File shell = new File(HOME_PATH, ".termux/shell");
|
||||
if (shell.exists()) {
|
||||
try {
|
||||
File canonicalFile = shell.getCanonicalFile();
|
||||
if (canonicalFile.isFile() && canonicalFile.canExecute()) {
|
||||
executablePath = canonicalFile.getName().equals("busybox") ? (PREFIX_PATH + "/bin/ash") : canonicalFile.getAbsolutePath();
|
||||
} else {
|
||||
Log.w(EmulatorDebug.LOG_TAG, "$HOME/.termux/shell points to non-executable shell: " + canonicalFile.getAbsolutePath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(EmulatorDebug.LOG_TAG, "Error checking $HOME/.termux/shell", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (executablePath == null) {
|
||||
// Try bash, zsh and ash in that order:
|
||||
for (String shellBinary : new String[] { "bash", "zsh", "ash" }) {
|
||||
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
||||
if (shellFile.canExecute()) {
|
||||
executablePath = shellFile.getAbsolutePath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (executablePath == null) {
|
||||
// Try bash, zsh and ash in that order:
|
||||
for (String shellBinary : new String[]{"bash", "zsh", "ash"}) {
|
||||
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
||||
if (shellFile.canExecute()) {
|
||||
executablePath = shellFile.getAbsolutePath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (executablePath == null) {
|
||||
// Fall back to system shell as last resort:
|
||||
executablePath = "/system/bin/sh";
|
||||
}
|
||||
if (executablePath == null) {
|
||||
// Fall back to system shell as last resort:
|
||||
executablePath = "/system/bin/sh";
|
||||
}
|
||||
|
||||
String[] parts = executablePath.split("/");
|
||||
shellName = "-" + parts[parts.length - 1];
|
||||
} else {
|
||||
int lastSlashIndex = executablePath.lastIndexOf('/');
|
||||
shellName = lastSlashIndex == -1 ? executablePath : executablePath.substring(lastSlashIndex + 1);
|
||||
}
|
||||
String[] parts = executablePath.split("/");
|
||||
shellName = "-" + parts[parts.length - 1];
|
||||
} else {
|
||||
int lastSlashIndex = executablePath.lastIndexOf('/');
|
||||
shellName = lastSlashIndex == -1 ? executablePath : executablePath.substring(lastSlashIndex + 1);
|
||||
}
|
||||
|
||||
String[] args;
|
||||
if (arguments == null) {
|
||||
args = new String[] { shellName };
|
||||
} else {
|
||||
args = new String[arguments.length + 1];
|
||||
args[0] = shellName;
|
||||
String[] args;
|
||||
if (arguments == null) {
|
||||
args = new String[]{shellName};
|
||||
} else {
|
||||
args = new String[arguments.length + 1];
|
||||
args[0] = shellName;
|
||||
|
||||
System.arraycopy(arguments, 0, args, 1, arguments.length);
|
||||
}
|
||||
System.arraycopy(arguments, 0, args, 1, arguments.length);
|
||||
}
|
||||
|
||||
TerminalSession session = new TerminalSession(executablePath, cwd, args, env, this);
|
||||
mTerminalSessions.add(session);
|
||||
updateNotification();
|
||||
return session;
|
||||
}
|
||||
TerminalSession session = new TerminalSession(executablePath, cwd, args, env, this);
|
||||
mTerminalSessions.add(session);
|
||||
updateNotification();
|
||||
return session;
|
||||
}
|
||||
|
||||
public int removeTermSession(TerminalSession sessionToRemove) {
|
||||
int indexOfRemoved = mTerminalSessions.indexOf(sessionToRemove);
|
||||
mTerminalSessions.remove(indexOfRemoved);
|
||||
if (mTerminalSessions.isEmpty() && mWakeLock == null) {
|
||||
// Finish if there are no sessions left and the wake lock is not held, otherwise keep the service alive if
|
||||
// holding wake lock since there may be daemon processes (e.g. sshd) running.
|
||||
stopSelf();
|
||||
} else {
|
||||
updateNotification();
|
||||
}
|
||||
return indexOfRemoved;
|
||||
}
|
||||
public int removeTermSession(TerminalSession sessionToRemove) {
|
||||
int indexOfRemoved = mTerminalSessions.indexOf(sessionToRemove);
|
||||
mTerminalSessions.remove(indexOfRemoved);
|
||||
if (mTerminalSessions.isEmpty() && mWakeLock == null) {
|
||||
// Finish if there are no sessions left and the wake lock is not held, otherwise keep the service alive if
|
||||
// holding wake lock since there may be daemon processes (e.g. sshd) running.
|
||||
stopSelf();
|
||||
} else {
|
||||
updateNotification();
|
||||
}
|
||||
return indexOfRemoved;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTitleChanged(TerminalSession changedSession) {
|
||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onTitleChanged(changedSession);
|
||||
}
|
||||
@Override
|
||||
public void onTitleChanged(TerminalSession changedSession) {
|
||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onTitleChanged(changedSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionFinished(final TerminalSession finishedSession) {
|
||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onSessionFinished(finishedSession);
|
||||
}
|
||||
@Override
|
||||
public void onSessionFinished(final TerminalSession finishedSession) {
|
||||
if (mSessionChangeCallback != null)
|
||||
mSessionChangeCallback.onSessionFinished(finishedSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(TerminalSession changedSession) {
|
||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onTextChanged(changedSession);
|
||||
}
|
||||
@Override
|
||||
public void onTextChanged(TerminalSession changedSession) {
|
||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onTextChanged(changedSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClipboardText(TerminalSession session, String text) {
|
||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onClipboardText(session, text);
|
||||
}
|
||||
@Override
|
||||
public void onClipboardText(TerminalSession session, String text) {
|
||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onClipboardText(session, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBell(TerminalSession session) {
|
||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onBell(session);
|
||||
}
|
||||
@Override
|
||||
public void onBell(TerminalSession session) {
|
||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onBell(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorsChanged(TerminalSession session) {
|
||||
|
|
|
@ -23,9 +23,9 @@ import java.util.LinkedList;
|
|||
/**
|
||||
* A document provider for the Storage Access Framework which exposes the files in the
|
||||
* $HOME/ folder to other apps.
|
||||
* <p>
|
||||
* <p/>
|
||||
* Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent:
|
||||
* <p>
|
||||
* <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."
|
||||
|
@ -172,7 +172,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
|
|||
/**
|
||||
* 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>
|
||||
* <p/>
|
||||
* The reverse of @{link #getFileForDocId}.
|
||||
*/
|
||||
private static String getDocIdForFile(File file) {
|
||||
|
|
|
@ -3,106 +3,106 @@ package com.termux.terminal;
|
|||
/** A circular byte buffer allowing one producer and one consumer thread. */
|
||||
final class ByteQueue {
|
||||
|
||||
private final byte[] mBuffer;
|
||||
private int mHead;
|
||||
private int mStoredBytes;
|
||||
private boolean mOpen = true;
|
||||
private final byte[] mBuffer;
|
||||
private int mHead;
|
||||
private int mStoredBytes;
|
||||
private boolean mOpen = true;
|
||||
|
||||
public ByteQueue(int size) {
|
||||
mBuffer = new byte[size];
|
||||
}
|
||||
public ByteQueue(int size) {
|
||||
mBuffer = new byte[size];
|
||||
}
|
||||
|
||||
public synchronized void close() {
|
||||
mOpen = false;
|
||||
notify();
|
||||
}
|
||||
public synchronized void close() {
|
||||
mOpen = false;
|
||||
notify();
|
||||
}
|
||||
|
||||
public synchronized int read(byte[] buffer, boolean block) {
|
||||
while (mStoredBytes == 0 && mOpen) {
|
||||
if (block) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!mOpen) return -1;
|
||||
public synchronized int read(byte[] buffer, boolean block) {
|
||||
while (mStoredBytes == 0 && mOpen) {
|
||||
if (block) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!mOpen) return -1;
|
||||
|
||||
int totalRead = 0;
|
||||
int bufferLength = mBuffer.length;
|
||||
boolean wasFull = bufferLength == mStoredBytes;
|
||||
int length = buffer.length;
|
||||
int offset = 0;
|
||||
while (length > 0 && mStoredBytes > 0) {
|
||||
int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
|
||||
int bytesToCopy = Math.min(length, oneRun);
|
||||
System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
|
||||
mHead += bytesToCopy;
|
||||
if (mHead >= bufferLength) mHead = 0;
|
||||
mStoredBytes -= bytesToCopy;
|
||||
length -= bytesToCopy;
|
||||
offset += bytesToCopy;
|
||||
totalRead += bytesToCopy;
|
||||
}
|
||||
if (wasFull) notify();
|
||||
return totalRead;
|
||||
}
|
||||
int totalRead = 0;
|
||||
int bufferLength = mBuffer.length;
|
||||
boolean wasFull = bufferLength == mStoredBytes;
|
||||
int length = buffer.length;
|
||||
int offset = 0;
|
||||
while (length > 0 && mStoredBytes > 0) {
|
||||
int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
|
||||
int bytesToCopy = Math.min(length, oneRun);
|
||||
System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
|
||||
mHead += bytesToCopy;
|
||||
if (mHead >= bufferLength) mHead = 0;
|
||||
mStoredBytes -= bytesToCopy;
|
||||
length -= bytesToCopy;
|
||||
offset += bytesToCopy;
|
||||
totalRead += bytesToCopy;
|
||||
}
|
||||
if (wasFull) notify();
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to write the specified portion of the provided buffer to the queue.
|
||||
*
|
||||
* Returns whether the output was totally written, false if it was closed before.
|
||||
*/
|
||||
public boolean write(byte[] buffer, int offset, int lengthToWrite) {
|
||||
if (lengthToWrite + offset > buffer.length) {
|
||||
throw new IllegalArgumentException("length + offset > buffer.length");
|
||||
} else if (lengthToWrite <= 0) {
|
||||
throw new IllegalArgumentException("length <= 0");
|
||||
}
|
||||
/**
|
||||
* Attempt to write the specified portion of the provided buffer to the queue.
|
||||
* <p/>
|
||||
* Returns whether the output was totally written, false if it was closed before.
|
||||
*/
|
||||
public boolean write(byte[] buffer, int offset, int lengthToWrite) {
|
||||
if (lengthToWrite + offset > buffer.length) {
|
||||
throw new IllegalArgumentException("length + offset > buffer.length");
|
||||
} else if (lengthToWrite <= 0) {
|
||||
throw new IllegalArgumentException("length <= 0");
|
||||
}
|
||||
|
||||
final int bufferLength = mBuffer.length;
|
||||
final int bufferLength = mBuffer.length;
|
||||
|
||||
synchronized (this) {
|
||||
while (lengthToWrite > 0) {
|
||||
while (bufferLength == mStoredBytes && mOpen) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
if (!mOpen) return false;
|
||||
final boolean wasEmpty = mStoredBytes == 0;
|
||||
int bytesToWriteBeforeWaiting = Math.min(lengthToWrite, bufferLength - mStoredBytes);
|
||||
lengthToWrite -= bytesToWriteBeforeWaiting;
|
||||
synchronized (this) {
|
||||
while (lengthToWrite > 0) {
|
||||
while (bufferLength == mStoredBytes && mOpen) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
if (!mOpen) return false;
|
||||
final boolean wasEmpty = mStoredBytes == 0;
|
||||
int bytesToWriteBeforeWaiting = Math.min(lengthToWrite, bufferLength - mStoredBytes);
|
||||
lengthToWrite -= bytesToWriteBeforeWaiting;
|
||||
|
||||
while (bytesToWriteBeforeWaiting > 0) {
|
||||
int tail = mHead + mStoredBytes;
|
||||
int oneRun;
|
||||
if (tail >= bufferLength) {
|
||||
// Buffer: [.............]
|
||||
// ________________H_______T
|
||||
// =>
|
||||
// Buffer: [.............]
|
||||
// ___________T____H
|
||||
// onRun= _____----_
|
||||
tail = tail - bufferLength;
|
||||
oneRun = mHead - tail;
|
||||
} else {
|
||||
oneRun = bufferLength - tail;
|
||||
}
|
||||
int bytesToCopy = Math.min(oneRun, bytesToWriteBeforeWaiting);
|
||||
System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
|
||||
offset += bytesToCopy;
|
||||
bytesToWriteBeforeWaiting -= bytesToCopy;
|
||||
mStoredBytes += bytesToCopy;
|
||||
}
|
||||
if (wasEmpty) notify();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
while (bytesToWriteBeforeWaiting > 0) {
|
||||
int tail = mHead + mStoredBytes;
|
||||
int oneRun;
|
||||
if (tail >= bufferLength) {
|
||||
// Buffer: [.............]
|
||||
// ________________H_______T
|
||||
// =>
|
||||
// Buffer: [.............]
|
||||
// ___________T____H
|
||||
// onRun= _____----_
|
||||
tail = tail - bufferLength;
|
||||
oneRun = mHead - tail;
|
||||
} else {
|
||||
oneRun = bufferLength - tail;
|
||||
}
|
||||
int bytesToCopy = Math.min(oneRun, bytesToWriteBeforeWaiting);
|
||||
System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
|
||||
offset += bytesToCopy;
|
||||
bytesToWriteBeforeWaiting -= bytesToCopy;
|
||||
mStoredBytes += bytesToCopy;
|
||||
}
|
||||
if (wasEmpty) notify();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import android.util.Log;
|
|||
|
||||
public final class EmulatorDebug {
|
||||
|
||||
/** The tag to use with {@link Log}. */
|
||||
public static final String LOG_TAG = "termux";
|
||||
/** The tag to use with {@link Log}. */
|
||||
public static final String LOG_TAG = "termux";
|
||||
|
||||
}
|
||||
|
|
|
@ -5,42 +5,37 @@ package com.termux.terminal;
|
|||
*/
|
||||
final class JNI {
|
||||
|
||||
static {
|
||||
System.loadLibrary("termux");
|
||||
}
|
||||
static {
|
||||
System.loadLibrary("termux");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the
|
||||
* subprocess.
|
||||
*
|
||||
* Callers are responsible for calling {@link #close(int)} on the returned file descriptor.
|
||||
*
|
||||
* @param cmd
|
||||
* The command to execute
|
||||
* @param cwd
|
||||
* The current working directory for the executed command
|
||||
* @param args
|
||||
* An array of arguments to the command
|
||||
* @param envVars
|
||||
* An array of strings of the form "VAR=value" to be added to the environment of the process
|
||||
* @param processId
|
||||
* A one-element array to which the process ID of the started process will be written.
|
||||
* @return the file descriptor resulting from opening /dev/ptmx master device. The sub process will have opened the
|
||||
* slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr.
|
||||
*/
|
||||
public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns);
|
||||
/**
|
||||
* Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the
|
||||
* subprocess.
|
||||
* <p/>
|
||||
* Callers are responsible for calling {@link #close(int)} on the returned file descriptor.
|
||||
*
|
||||
* @param cmd The command to execute
|
||||
* @param cwd The current working directory for the executed command
|
||||
* @param args An array of arguments to the command
|
||||
* @param envVars An array of strings of the form "VAR=value" to be added to the environment of the process
|
||||
* @param processId A one-element array to which the process ID of the started process will be written.
|
||||
* @return the file descriptor resulting from opening /dev/ptmx master device. The sub process will have opened the
|
||||
* slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr.
|
||||
*/
|
||||
public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns);
|
||||
|
||||
/** Set the window size for a given pty, which allows connected programs to learn how large their screen is. */
|
||||
public static native void setPtyWindowSize(int fd, int rows, int cols);
|
||||
/** Set the window size for a given pty, which allows connected programs to learn how large their screen is. */
|
||||
public static native void setPtyWindowSize(int fd, int rows, int cols);
|
||||
|
||||
/**
|
||||
* Causes the calling thread to wait for the process associated with the receiver to finish executing.
|
||||
*
|
||||
* @return if >= 0, the exit status of the process. If < 0, the signal causing the process to stop negated.
|
||||
*/
|
||||
public static native int waitFor(int processId);
|
||||
/**
|
||||
* Causes the calling thread to wait for the process associated with the receiver to finish executing.
|
||||
*
|
||||
* @return if >= 0, the exit status of the process. If < 0, the signal causing the process to stop negated.
|
||||
*/
|
||||
public static native int waitFor(int processId);
|
||||
|
||||
/** Close a file descriptor through the close(2) system call. */
|
||||
public static native void close(int fileDescriptor);
|
||||
/** Close a file descriptor through the close(2) system call. */
|
||||
public static native void close(int fileDescriptor);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.termux.terminal;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static android.view.KeyEvent.KEYCODE_BREAK;
|
||||
import static android.view.KeyEvent.KEYCODE_DEL;
|
||||
import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
|
||||
|
@ -22,6 +27,7 @@ import static android.view.KeyEvent.KEYCODE_F7;
|
|||
import static android.view.KeyEvent.KEYCODE_F8;
|
||||
import static android.view.KeyEvent.KEYCODE_F9;
|
||||
import static android.view.KeyEvent.KEYCODE_FORWARD_DEL;
|
||||
import static android.view.KeyEvent.KEYCODE_HOME;
|
||||
import static android.view.KeyEvent.KEYCODE_INSERT;
|
||||
import static android.view.KeyEvent.KEYCODE_MOVE_END;
|
||||
import static android.view.KeyEvent.KEYCODE_NUMPAD_0;
|
||||
|
@ -47,264 +53,259 @@ import static android.view.KeyEvent.KEYCODE_PAGE_DOWN;
|
|||
import static android.view.KeyEvent.KEYCODE_PAGE_UP;
|
||||
import static android.view.KeyEvent.KEYCODE_SYSRQ;
|
||||
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 static final int KEYMOD_ALT = 0x80000000;
|
||||
public static final int KEYMOD_CTRL = 0x40000000;
|
||||
public static final int KEYMOD_SHIFT = 0x20000000;
|
||||
public static final int KEYMOD_ALT = 0x80000000;
|
||||
public static final int KEYMOD_CTRL = 0x40000000;
|
||||
public static final int KEYMOD_SHIFT = 0x20000000;
|
||||
|
||||
private static final Map<String, Integer> TERMCAP_TO_KEYCODE = new HashMap<>();
|
||||
static {
|
||||
// terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html
|
||||
// termcap: http://man7.org/linux/man-pages/man5/termcap.5.html
|
||||
TERMCAP_TO_KEYCODE.put("%i", KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT);
|
||||
TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_HOME); // Shifted home
|
||||
TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT);
|
||||
TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key
|
||||
private static final Map<String, Integer> TERMCAP_TO_KEYCODE = new HashMap<>();
|
||||
|
||||
TERMCAP_TO_KEYCODE.put("k1", KEYCODE_F1);
|
||||
TERMCAP_TO_KEYCODE.put("k2", KEYCODE_F2);
|
||||
TERMCAP_TO_KEYCODE.put("k3", KEYCODE_F3);
|
||||
TERMCAP_TO_KEYCODE.put("k4", KEYCODE_F4);
|
||||
TERMCAP_TO_KEYCODE.put("k5", KEYCODE_F5);
|
||||
TERMCAP_TO_KEYCODE.put("k6", KEYCODE_F6);
|
||||
TERMCAP_TO_KEYCODE.put("k7", KEYCODE_F7);
|
||||
TERMCAP_TO_KEYCODE.put("k8", KEYCODE_F8);
|
||||
TERMCAP_TO_KEYCODE.put("k9", KEYCODE_F9);
|
||||
TERMCAP_TO_KEYCODE.put("k;", KEYCODE_F10);
|
||||
TERMCAP_TO_KEYCODE.put("F1", KEYCODE_F11);
|
||||
TERMCAP_TO_KEYCODE.put("F2", KEYCODE_F12);
|
||||
TERMCAP_TO_KEYCODE.put("F3", KEYMOD_SHIFT | KEYCODE_F1);
|
||||
TERMCAP_TO_KEYCODE.put("F4", KEYMOD_SHIFT | KEYCODE_F2);
|
||||
TERMCAP_TO_KEYCODE.put("F5", KEYMOD_SHIFT | KEYCODE_F3);
|
||||
TERMCAP_TO_KEYCODE.put("F6", KEYMOD_SHIFT | KEYCODE_F4);
|
||||
TERMCAP_TO_KEYCODE.put("F7", KEYMOD_SHIFT | KEYCODE_F5);
|
||||
TERMCAP_TO_KEYCODE.put("F8", KEYMOD_SHIFT | KEYCODE_F6);
|
||||
TERMCAP_TO_KEYCODE.put("F9", KEYMOD_SHIFT | KEYCODE_F7);
|
||||
TERMCAP_TO_KEYCODE.put("FA", KEYMOD_SHIFT | KEYCODE_F8);
|
||||
TERMCAP_TO_KEYCODE.put("FB", KEYMOD_SHIFT | KEYCODE_F9);
|
||||
TERMCAP_TO_KEYCODE.put("FC", KEYMOD_SHIFT | KEYCODE_F10);
|
||||
TERMCAP_TO_KEYCODE.put("FD", KEYMOD_SHIFT | KEYCODE_F11);
|
||||
TERMCAP_TO_KEYCODE.put("FE", KEYMOD_SHIFT | KEYCODE_F12);
|
||||
static {
|
||||
// terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html
|
||||
// termcap: http://man7.org/linux/man-pages/man5/termcap.5.html
|
||||
TERMCAP_TO_KEYCODE.put("%i", KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT);
|
||||
TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_HOME); // Shifted home
|
||||
TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT);
|
||||
TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key
|
||||
|
||||
TERMCAP_TO_KEYCODE.put("kb", KEYCODE_DEL); // backspace key
|
||||
TERMCAP_TO_KEYCODE.put("k1", KEYCODE_F1);
|
||||
TERMCAP_TO_KEYCODE.put("k2", KEYCODE_F2);
|
||||
TERMCAP_TO_KEYCODE.put("k3", KEYCODE_F3);
|
||||
TERMCAP_TO_KEYCODE.put("k4", KEYCODE_F4);
|
||||
TERMCAP_TO_KEYCODE.put("k5", KEYCODE_F5);
|
||||
TERMCAP_TO_KEYCODE.put("k6", KEYCODE_F6);
|
||||
TERMCAP_TO_KEYCODE.put("k7", KEYCODE_F7);
|
||||
TERMCAP_TO_KEYCODE.put("k8", KEYCODE_F8);
|
||||
TERMCAP_TO_KEYCODE.put("k9", KEYCODE_F9);
|
||||
TERMCAP_TO_KEYCODE.put("k;", KEYCODE_F10);
|
||||
TERMCAP_TO_KEYCODE.put("F1", KEYCODE_F11);
|
||||
TERMCAP_TO_KEYCODE.put("F2", KEYCODE_F12);
|
||||
TERMCAP_TO_KEYCODE.put("F3", KEYMOD_SHIFT | KEYCODE_F1);
|
||||
TERMCAP_TO_KEYCODE.put("F4", KEYMOD_SHIFT | KEYCODE_F2);
|
||||
TERMCAP_TO_KEYCODE.put("F5", KEYMOD_SHIFT | KEYCODE_F3);
|
||||
TERMCAP_TO_KEYCODE.put("F6", KEYMOD_SHIFT | KEYCODE_F4);
|
||||
TERMCAP_TO_KEYCODE.put("F7", KEYMOD_SHIFT | KEYCODE_F5);
|
||||
TERMCAP_TO_KEYCODE.put("F8", KEYMOD_SHIFT | KEYCODE_F6);
|
||||
TERMCAP_TO_KEYCODE.put("F9", KEYMOD_SHIFT | KEYCODE_F7);
|
||||
TERMCAP_TO_KEYCODE.put("FA", KEYMOD_SHIFT | KEYCODE_F8);
|
||||
TERMCAP_TO_KEYCODE.put("FB", KEYMOD_SHIFT | KEYCODE_F9);
|
||||
TERMCAP_TO_KEYCODE.put("FC", KEYMOD_SHIFT | KEYCODE_F10);
|
||||
TERMCAP_TO_KEYCODE.put("FD", KEYMOD_SHIFT | KEYCODE_F11);
|
||||
TERMCAP_TO_KEYCODE.put("FE", KEYMOD_SHIFT | KEYCODE_F12);
|
||||
|
||||
TERMCAP_TO_KEYCODE.put("kd", KEYCODE_DPAD_DOWN); // terminfo=kcud1, down-arrow key
|
||||
TERMCAP_TO_KEYCODE.put("kh", KeyEvent.KEYCODE_HOME);
|
||||
TERMCAP_TO_KEYCODE.put("kl", KEYCODE_DPAD_LEFT);
|
||||
TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT);
|
||||
TERMCAP_TO_KEYCODE.put("kb", KEYCODE_DEL); // backspace key
|
||||
|
||||
// K1=Upper left of keypad:
|
||||
// t_K1 <kHome> keypad home key
|
||||
// t_K3 <kPageUp> keypad page-up key
|
||||
// t_K4 <kEnd> keypad end key
|
||||
// t_K5 <kPageDown> keypad page-down key
|
||||
TERMCAP_TO_KEYCODE.put("K1", KeyEvent.KEYCODE_HOME);
|
||||
TERMCAP_TO_KEYCODE.put("K3", KeyEvent.KEYCODE_PAGE_UP);
|
||||
TERMCAP_TO_KEYCODE.put("K4", KeyEvent.KEYCODE_MOVE_END);
|
||||
TERMCAP_TO_KEYCODE.put("K5", KeyEvent.KEYCODE_PAGE_DOWN);
|
||||
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("kl", KEYCODE_DPAD_LEFT);
|
||||
TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT);
|
||||
|
||||
TERMCAP_TO_KEYCODE.put("ku", KEYCODE_DPAD_UP);
|
||||
// K1=Upper left of keypad:
|
||||
// t_K1 <kHome> keypad home key
|
||||
// t_K3 <kPageUp> keypad page-up key
|
||||
// t_K4 <kEnd> keypad end key
|
||||
// t_K5 <kPageDown> keypad page-down key
|
||||
TERMCAP_TO_KEYCODE.put("K1", KeyEvent.KEYCODE_HOME);
|
||||
TERMCAP_TO_KEYCODE.put("K3", KeyEvent.KEYCODE_PAGE_UP);
|
||||
TERMCAP_TO_KEYCODE.put("K4", KeyEvent.KEYCODE_MOVE_END);
|
||||
TERMCAP_TO_KEYCODE.put("K5", KeyEvent.KEYCODE_PAGE_DOWN);
|
||||
|
||||
TERMCAP_TO_KEYCODE.put("kB", KEYMOD_SHIFT | KEYCODE_TAB); // termcap=kB, terminfo=kcbt: Back-tab
|
||||
TERMCAP_TO_KEYCODE.put("kD", KEYCODE_FORWARD_DEL); // terminfo=kdch1, delete-character key
|
||||
TERMCAP_TO_KEYCODE.put("kDN", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // non-standard shifted arrow down
|
||||
TERMCAP_TO_KEYCODE.put("kF", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // terminfo=kind, scroll-forward key
|
||||
TERMCAP_TO_KEYCODE.put("kI", KEYCODE_INSERT);
|
||||
TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_UP);
|
||||
TERMCAP_TO_KEYCODE.put("kP", KEYCODE_PAGE_DOWN);
|
||||
TERMCAP_TO_KEYCODE.put("kR", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // terminfo=kri, scroll-backward key
|
||||
TERMCAP_TO_KEYCODE.put("kUP", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // non-standard shifted up
|
||||
TERMCAP_TO_KEYCODE.put("ku", KEYCODE_DPAD_UP);
|
||||
|
||||
TERMCAP_TO_KEYCODE.put("@7", KEYCODE_MOVE_END);
|
||||
TERMCAP_TO_KEYCODE.put("@8", KEYCODE_NUMPAD_ENTER);
|
||||
}
|
||||
TERMCAP_TO_KEYCODE.put("kB", KEYMOD_SHIFT | KEYCODE_TAB); // termcap=kB, terminfo=kcbt: Back-tab
|
||||
TERMCAP_TO_KEYCODE.put("kD", KEYCODE_FORWARD_DEL); // terminfo=kdch1, delete-character key
|
||||
TERMCAP_TO_KEYCODE.put("kDN", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // non-standard shifted arrow down
|
||||
TERMCAP_TO_KEYCODE.put("kF", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // terminfo=kind, scroll-forward key
|
||||
TERMCAP_TO_KEYCODE.put("kI", KEYCODE_INSERT);
|
||||
TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_UP);
|
||||
TERMCAP_TO_KEYCODE.put("kP", KEYCODE_PAGE_DOWN);
|
||||
TERMCAP_TO_KEYCODE.put("kR", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // terminfo=kri, scroll-backward key
|
||||
TERMCAP_TO_KEYCODE.put("kUP", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // non-standard shifted up
|
||||
|
||||
static String getCodeFromTermcap(String termcap, boolean cursorKeysApplication, boolean keypadApplication) {
|
||||
Integer keyCodeAndMod = TERMCAP_TO_KEYCODE.get(termcap);
|
||||
if (keyCodeAndMod == null) return null;
|
||||
int keyCode = keyCodeAndMod;
|
||||
int keyMod = 0;
|
||||
if ((keyCode & KEYMOD_SHIFT) != 0) {
|
||||
keyMod |= KEYMOD_SHIFT;
|
||||
keyCode &= ~KEYMOD_SHIFT;
|
||||
}
|
||||
if ((keyCode & KEYMOD_CTRL) != 0) {
|
||||
keyMod |= KEYMOD_CTRL;
|
||||
keyCode &= ~KEYMOD_CTRL;
|
||||
}
|
||||
if ((keyCode & KEYMOD_ALT) != 0) {
|
||||
keyMod |= KEYMOD_ALT;
|
||||
keyCode &= ~KEYMOD_ALT;
|
||||
}
|
||||
return getCode(keyCode, keyMod, cursorKeysApplication, keypadApplication);
|
||||
}
|
||||
TERMCAP_TO_KEYCODE.put("@7", KEYCODE_MOVE_END);
|
||||
TERMCAP_TO_KEYCODE.put("@8", KEYCODE_NUMPAD_ENTER);
|
||||
}
|
||||
|
||||
public static String getCode(int keyCode, int keyMode, boolean cursorApp, boolean keypadApplication) {
|
||||
switch (keyCode) {
|
||||
case KEYCODE_DPAD_CENTER:
|
||||
return "\015";
|
||||
static String getCodeFromTermcap(String termcap, boolean cursorKeysApplication, boolean keypadApplication) {
|
||||
Integer keyCodeAndMod = TERMCAP_TO_KEYCODE.get(termcap);
|
||||
if (keyCodeAndMod == null) return null;
|
||||
int keyCode = keyCodeAndMod;
|
||||
int keyMod = 0;
|
||||
if ((keyCode & KEYMOD_SHIFT) != 0) {
|
||||
keyMod |= KEYMOD_SHIFT;
|
||||
keyCode &= ~KEYMOD_SHIFT;
|
||||
}
|
||||
if ((keyCode & KEYMOD_CTRL) != 0) {
|
||||
keyMod |= KEYMOD_CTRL;
|
||||
keyCode &= ~KEYMOD_CTRL;
|
||||
}
|
||||
if ((keyCode & KEYMOD_ALT) != 0) {
|
||||
keyMod |= KEYMOD_ALT;
|
||||
keyCode &= ~KEYMOD_ALT;
|
||||
}
|
||||
return getCode(keyCode, keyMod, cursorKeysApplication, keypadApplication);
|
||||
}
|
||||
|
||||
case KEYCODE_DPAD_UP:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OA" : "\033[A") : transformForModifiers("\033[1", keyMode, 'A');
|
||||
case KEYCODE_DPAD_DOWN:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OB" : "\033[B") : transformForModifiers("\033[1", keyMode, 'B');
|
||||
case KEYCODE_DPAD_RIGHT:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OC" : "\033[C") : transformForModifiers("\033[1", keyMode, 'C');
|
||||
case KEYCODE_DPAD_LEFT:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D');
|
||||
public static String getCode(int keyCode, int keyMode, boolean cursorApp, boolean keypadApplication) {
|
||||
switch (keyCode) {
|
||||
case KEYCODE_DPAD_CENTER:
|
||||
return "\015";
|
||||
|
||||
case KeyEvent.KEYCODE_HOME:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H');
|
||||
case KEYCODE_MOVE_END:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F');
|
||||
case KEYCODE_DPAD_UP:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OA" : "\033[A") : transformForModifiers("\033[1", keyMode, 'A');
|
||||
case KEYCODE_DPAD_DOWN:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OB" : "\033[B") : transformForModifiers("\033[1", keyMode, 'B');
|
||||
case KEYCODE_DPAD_RIGHT:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OC" : "\033[C") : transformForModifiers("\033[1", keyMode, 'C');
|
||||
case KEYCODE_DPAD_LEFT:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D');
|
||||
|
||||
// An xterm can send function keys F1 to F4 in two modes: vt100 compatible or
|
||||
// not. Because Vim may not know what the xterm is sending, both types of keys
|
||||
// are recognized. The same happens for the <Home> and <End> keys.
|
||||
// normal vt100 ~
|
||||
// <F1> t_k1 <Esc>[11~ <xF1> <Esc>OP *<xF1>-xterm*
|
||||
// <F2> t_k2 <Esc>[12~ <xF2> <Esc>OQ *<xF2>-xterm*
|
||||
// <F3> t_k3 <Esc>[13~ <xF3> <Esc>OR *<xF3>-xterm*
|
||||
// <F4> t_k4 <Esc>[14~ <xF4> <Esc>OS *<xF4>-xterm*
|
||||
// <Home> t_kh <Esc>[7~ <xHome> <Esc>OH *<xHome>-xterm*
|
||||
// <End> t_@7 <Esc>[4~ <xEnd> <Esc>OF *<xEnd>-xterm*
|
||||
case KEYCODE_F1:
|
||||
return (keyMode == 0) ? "\033OP" : transformForModifiers("\033[1", keyMode, 'P');
|
||||
case KEYCODE_F2:
|
||||
return (keyMode == 0) ? "\033OQ" : transformForModifiers("\033[1", keyMode, 'Q');
|
||||
case KEYCODE_F3:
|
||||
return (keyMode == 0) ? "\033OR" : transformForModifiers("\033[1", keyMode, 'R');
|
||||
case KEYCODE_F4:
|
||||
return (keyMode == 0) ? "\033OS" : transformForModifiers("\033[1", keyMode, 'S');
|
||||
case KEYCODE_F5:
|
||||
return transformForModifiers("\033[15", keyMode, '~');
|
||||
case KEYCODE_F6:
|
||||
return transformForModifiers("\033[17", keyMode, '~');
|
||||
case KEYCODE_F7:
|
||||
return transformForModifiers("\033[18", keyMode, '~');
|
||||
case KEYCODE_F8:
|
||||
return transformForModifiers("\033[19", keyMode, '~');
|
||||
case KEYCODE_F9:
|
||||
return transformForModifiers("\033[20", keyMode, '~');
|
||||
case KEYCODE_F10:
|
||||
return transformForModifiers("\033[21", keyMode, '~');
|
||||
case KEYCODE_F11:
|
||||
return transformForModifiers("\033[23", keyMode, '~');
|
||||
case KEYCODE_F12:
|
||||
return transformForModifiers("\033[24", keyMode, '~');
|
||||
case KeyEvent.KEYCODE_HOME:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H');
|
||||
case KEYCODE_MOVE_END:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F');
|
||||
|
||||
case KEYCODE_SYSRQ:
|
||||
return "\033[32~"; // Sys Request / Print
|
||||
// Is this Scroll lock? case Cancel: return "\033[33~";
|
||||
case KEYCODE_BREAK:
|
||||
return "\033[34~"; // Pause/Break
|
||||
// An xterm can send function keys F1 to F4 in two modes: vt100 compatible or
|
||||
// not. Because Vim may not know what the xterm is sending, both types of keys
|
||||
// are recognized. The same happens for the <Home> and <End> keys.
|
||||
// normal vt100 ~
|
||||
// <F1> t_k1 <Esc>[11~ <xF1> <Esc>OP *<xF1>-xterm*
|
||||
// <F2> t_k2 <Esc>[12~ <xF2> <Esc>OQ *<xF2>-xterm*
|
||||
// <F3> t_k3 <Esc>[13~ <xF3> <Esc>OR *<xF3>-xterm*
|
||||
// <F4> t_k4 <Esc>[14~ <xF4> <Esc>OS *<xF4>-xterm*
|
||||
// <Home> t_kh <Esc>[7~ <xHome> <Esc>OH *<xHome>-xterm*
|
||||
// <End> t_@7 <Esc>[4~ <xEnd> <Esc>OF *<xEnd>-xterm*
|
||||
case KEYCODE_F1:
|
||||
return (keyMode == 0) ? "\033OP" : transformForModifiers("\033[1", keyMode, 'P');
|
||||
case KEYCODE_F2:
|
||||
return (keyMode == 0) ? "\033OQ" : transformForModifiers("\033[1", keyMode, 'Q');
|
||||
case KEYCODE_F3:
|
||||
return (keyMode == 0) ? "\033OR" : transformForModifiers("\033[1", keyMode, 'R');
|
||||
case KEYCODE_F4:
|
||||
return (keyMode == 0) ? "\033OS" : transformForModifiers("\033[1", keyMode, 'S');
|
||||
case KEYCODE_F5:
|
||||
return transformForModifiers("\033[15", keyMode, '~');
|
||||
case KEYCODE_F6:
|
||||
return transformForModifiers("\033[17", keyMode, '~');
|
||||
case KEYCODE_F7:
|
||||
return transformForModifiers("\033[18", keyMode, '~');
|
||||
case KEYCODE_F8:
|
||||
return transformForModifiers("\033[19", keyMode, '~');
|
||||
case KEYCODE_F9:
|
||||
return transformForModifiers("\033[20", keyMode, '~');
|
||||
case KEYCODE_F10:
|
||||
return transformForModifiers("\033[21", keyMode, '~');
|
||||
case KEYCODE_F11:
|
||||
return transformForModifiers("\033[23", keyMode, '~');
|
||||
case KEYCODE_F12:
|
||||
return transformForModifiers("\033[24", keyMode, '~');
|
||||
|
||||
case KEYCODE_ESCAPE:
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
return "\033";
|
||||
case KEYCODE_SYSRQ:
|
||||
return "\033[32~"; // Sys Request / Print
|
||||
// Is this Scroll lock? case Cancel: return "\033[33~";
|
||||
case KEYCODE_BREAK:
|
||||
return "\033[34~"; // Pause/Break
|
||||
|
||||
case KEYCODE_INSERT:
|
||||
return transformForModifiers("\033[2", keyMode, '~');
|
||||
case KEYCODE_FORWARD_DEL:
|
||||
return transformForModifiers("\033[3", keyMode, '~');
|
||||
case KEYCODE_ESCAPE:
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
return "\033";
|
||||
|
||||
case KEYCODE_NUMPAD_DOT:
|
||||
return keypadApplication ? "\033On" : "\033[3~";
|
||||
case KEYCODE_INSERT:
|
||||
return transformForModifiers("\033[2", keyMode, '~');
|
||||
case KEYCODE_FORWARD_DEL:
|
||||
return transformForModifiers("\033[3", keyMode, '~');
|
||||
|
||||
case KEYCODE_PAGE_UP:
|
||||
return "\033[5~";
|
||||
case KEYCODE_PAGE_DOWN:
|
||||
return "\033[6~";
|
||||
case KEYCODE_DEL:
|
||||
// Yes, this needs to U+007F and not U+0008!
|
||||
return "\u007F";
|
||||
case KEYCODE_NUM_LOCK:
|
||||
return "\033OP";
|
||||
case KEYCODE_NUMPAD_DOT:
|
||||
return keypadApplication ? "\033On" : "\033[3~";
|
||||
|
||||
case KeyEvent.KEYCODE_SPACE:
|
||||
// If ctrl is not down, return null so that it goes through normal input processing (which may e.g. cause a
|
||||
// combining accent to be written):
|
||||
return ((keyMode & KEYMOD_CTRL) == 0) ? null : "\0";
|
||||
case KEYCODE_TAB:
|
||||
// This is back-tab when shifted:
|
||||
return (keyMode & KEYMOD_SHIFT) == 0 ? "\011" : "\033[Z";
|
||||
case KEYCODE_ENTER:
|
||||
return ((keyMode & KEYMOD_ALT) == 0) ? "\r" : "\033\r";
|
||||
case KEYCODE_PAGE_UP:
|
||||
return "\033[5~";
|
||||
case KEYCODE_PAGE_DOWN:
|
||||
return "\033[6~";
|
||||
case KEYCODE_DEL:
|
||||
// Yes, this needs to U+007F and not U+0008!
|
||||
return "\u007F";
|
||||
case KEYCODE_NUM_LOCK:
|
||||
return "\033OP";
|
||||
|
||||
case KEYCODE_NUMPAD_ENTER:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'M') : "\n";
|
||||
case KEYCODE_NUMPAD_MULTIPLY:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'j') : "*";
|
||||
case KEYCODE_NUMPAD_ADD:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+";
|
||||
case KEYCODE_NUMPAD_COMMA:
|
||||
return ",";
|
||||
case KEYCODE_NUMPAD_SUBTRACT:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
|
||||
case KEYCODE_NUMPAD_DIVIDE:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
|
||||
case KEYCODE_NUMPAD_0:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "1";
|
||||
case KEYCODE_NUMPAD_1:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
|
||||
case KEYCODE_NUMPAD_2:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'r') : "2";
|
||||
case KEYCODE_NUMPAD_3:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 's') : "3";
|
||||
case KEYCODE_NUMPAD_4:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 't') : "4";
|
||||
case KEYCODE_NUMPAD_5:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'u') : "5";
|
||||
case KEYCODE_NUMPAD_6:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'v') : "6";
|
||||
case KEYCODE_NUMPAD_7:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'w') : "7";
|
||||
case KEYCODE_NUMPAD_8:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'x') : "8";
|
||||
case KEYCODE_NUMPAD_9:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'y') : "9";
|
||||
case KEYCODE_NUMPAD_EQUALS:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'X') : "=";
|
||||
}
|
||||
case KeyEvent.KEYCODE_SPACE:
|
||||
// If ctrl is not down, return null so that it goes through normal input processing (which may e.g. cause a
|
||||
// combining accent to be written):
|
||||
return ((keyMode & KEYMOD_CTRL) == 0) ? null : "\0";
|
||||
case KEYCODE_TAB:
|
||||
// This is back-tab when shifted:
|
||||
return (keyMode & KEYMOD_SHIFT) == 0 ? "\011" : "\033[Z";
|
||||
case KEYCODE_ENTER:
|
||||
return ((keyMode & KEYMOD_ALT) == 0) ? "\r" : "\033\r";
|
||||
|
||||
return null;
|
||||
}
|
||||
case KEYCODE_NUMPAD_ENTER:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'M') : "\n";
|
||||
case KEYCODE_NUMPAD_MULTIPLY:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'j') : "*";
|
||||
case KEYCODE_NUMPAD_ADD:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+";
|
||||
case KEYCODE_NUMPAD_COMMA:
|
||||
return ",";
|
||||
case KEYCODE_NUMPAD_SUBTRACT:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
|
||||
case KEYCODE_NUMPAD_DIVIDE:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
|
||||
case KEYCODE_NUMPAD_0:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "1";
|
||||
case KEYCODE_NUMPAD_1:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
|
||||
case KEYCODE_NUMPAD_2:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'r') : "2";
|
||||
case KEYCODE_NUMPAD_3:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 's') : "3";
|
||||
case KEYCODE_NUMPAD_4:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 't') : "4";
|
||||
case KEYCODE_NUMPAD_5:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'u') : "5";
|
||||
case KEYCODE_NUMPAD_6:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'v') : "6";
|
||||
case KEYCODE_NUMPAD_7:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'w') : "7";
|
||||
case KEYCODE_NUMPAD_8:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'x') : "8";
|
||||
case KEYCODE_NUMPAD_9:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'y') : "9";
|
||||
case KEYCODE_NUMPAD_EQUALS:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'X') : "=";
|
||||
}
|
||||
|
||||
private static String transformForModifiers(String start, int keymod, char lastChar) {
|
||||
int modifier;
|
||||
switch (keymod) {
|
||||
case KEYMOD_SHIFT:
|
||||
modifier = 2;
|
||||
break;
|
||||
case KEYMOD_ALT:
|
||||
modifier = 3;
|
||||
break;
|
||||
case (KEYMOD_SHIFT | KEYMOD_ALT):
|
||||
modifier = 4;
|
||||
break;
|
||||
case KEYMOD_CTRL:
|
||||
modifier = 5;
|
||||
break;
|
||||
case KEYMOD_SHIFT | KEYMOD_CTRL:
|
||||
modifier = 6;
|
||||
break;
|
||||
case KEYMOD_ALT | KEYMOD_CTRL:
|
||||
modifier = 7;
|
||||
break;
|
||||
case KEYMOD_SHIFT | KEYMOD_ALT | KEYMOD_CTRL:
|
||||
modifier = 8;
|
||||
break;
|
||||
default:
|
||||
return start + lastChar;
|
||||
}
|
||||
return start + (";" + modifier) + lastChar;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String transformForModifiers(String start, int keymod, char lastChar) {
|
||||
int modifier;
|
||||
switch (keymod) {
|
||||
case KEYMOD_SHIFT:
|
||||
modifier = 2;
|
||||
break;
|
||||
case KEYMOD_ALT:
|
||||
modifier = 3;
|
||||
break;
|
||||
case (KEYMOD_SHIFT | KEYMOD_ALT):
|
||||
modifier = 4;
|
||||
break;
|
||||
case KEYMOD_CTRL:
|
||||
modifier = 5;
|
||||
break;
|
||||
case KEYMOD_SHIFT | KEYMOD_CTRL:
|
||||
modifier = 6;
|
||||
break;
|
||||
case KEYMOD_ALT | KEYMOD_CTRL:
|
||||
modifier = 7;
|
||||
break;
|
||||
case KEYMOD_SHIFT | KEYMOD_ALT | KEYMOD_CTRL:
|
||||
modifier = 8;
|
||||
break;
|
||||
default:
|
||||
return start + lastChar;
|
||||
}
|
||||
return start + (";" + modifier) + lastChar;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,441 +3,425 @@ 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
|
||||
* history.
|
||||
*
|
||||
* <p/>
|
||||
* See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices.
|
||||
*/
|
||||
public final class TerminalBuffer {
|
||||
|
||||
TerminalRow[] mLines;
|
||||
/** The length of {@link #mLines}. */
|
||||
int mTotalRows;
|
||||
/** The number of rows and columns visible on the screen. */
|
||||
int mScreenRows, mColumns;
|
||||
/** The number of rows kept in history. */
|
||||
private int mActiveTranscriptRows = 0;
|
||||
/** The index in the circular buffer where the visible screen starts. */
|
||||
private int mScreenFirstRow = 0;
|
||||
TerminalRow[] mLines;
|
||||
/** The length of {@link #mLines}. */
|
||||
int mTotalRows;
|
||||
/** The number of rows and columns visible on the screen. */
|
||||
int mScreenRows, mColumns;
|
||||
/** The number of rows kept in history. */
|
||||
private int mActiveTranscriptRows = 0;
|
||||
/** The index in the circular buffer where the visible screen starts. */
|
||||
private int mScreenFirstRow = 0;
|
||||
|
||||
/**
|
||||
* Create a transcript screen.
|
||||
*
|
||||
* @param columns
|
||||
* the width of the screen in characters.
|
||||
* @param totalRows
|
||||
* the height of the entire text area, in rows of text.
|
||||
* @param screenRows
|
||||
* the height of just the screen, not including the transcript that holds lines that have scrolled off
|
||||
* the top of the screen.
|
||||
*/
|
||||
public TerminalBuffer(int columns, int totalRows, int screenRows) {
|
||||
mColumns = columns;
|
||||
mTotalRows = totalRows;
|
||||
mScreenRows = screenRows;
|
||||
mLines = new TerminalRow[totalRows];
|
||||
/**
|
||||
* Create a transcript screen.
|
||||
*
|
||||
* @param columns the width of the screen in characters.
|
||||
* @param totalRows the height of the entire text area, in rows of text.
|
||||
* @param screenRows the height of just the screen, not including the transcript that holds lines that have scrolled off
|
||||
* the top of the screen.
|
||||
*/
|
||||
public TerminalBuffer(int columns, int totalRows, int screenRows) {
|
||||
mColumns = columns;
|
||||
mTotalRows = totalRows;
|
||||
mScreenRows = screenRows;
|
||||
mLines = new TerminalRow[totalRows];
|
||||
|
||||
blockSet(0, 0, columns, screenRows, ' ', TextStyle.NORMAL);
|
||||
}
|
||||
blockSet(0, 0, columns, screenRows, ' ', TextStyle.NORMAL);
|
||||
}
|
||||
|
||||
public String getTranscriptText() {
|
||||
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim();
|
||||
}
|
||||
public String getTranscriptText() {
|
||||
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim();
|
||||
}
|
||||
|
||||
public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final int columns = mColumns;
|
||||
public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final int columns = mColumns;
|
||||
|
||||
if (selY1 < -getActiveTranscriptRows()) selY1 = -getActiveTranscriptRows();
|
||||
if (selY2 >= mScreenRows) selY2 = mScreenRows - 1;
|
||||
if (selY1 < -getActiveTranscriptRows()) selY1 = -getActiveTranscriptRows();
|
||||
if (selY2 >= mScreenRows) selY2 = mScreenRows - 1;
|
||||
|
||||
for (int row = selY1; row <= selY2; row++) {
|
||||
int x1 = (row == selY1) ? selX1 : 0;
|
||||
int x2;
|
||||
if (row == selY2) {
|
||||
x2 = selX2 + 1;
|
||||
if (x2 > columns) x2 = columns;
|
||||
} else {
|
||||
x2 = columns;
|
||||
}
|
||||
TerminalRow lineObject = mLines[externalToInternalRow(row)];
|
||||
int x1Index = lineObject.findStartOfColumn(x1);
|
||||
int x2Index = (x2 < mColumns) ? lineObject.findStartOfColumn(x2) : lineObject.getSpaceUsed();
|
||||
if (x2Index == x1Index) {
|
||||
// Selected the start of a wide character.
|
||||
x2Index = lineObject.findStartOfColumn(x2+1);
|
||||
}
|
||||
char[] line = lineObject.mText;
|
||||
int lastPrintingCharIndex = -1;
|
||||
int i;
|
||||
boolean rowLineWrap = getLineWrap(row);
|
||||
if (rowLineWrap && x2 == columns) {
|
||||
// If the line was wrapped, we shouldn't lose trailing space:
|
||||
lastPrintingCharIndex = x2Index - 1;
|
||||
} else {
|
||||
for (i = x1Index; i < x2Index; ++i) {
|
||||
char c = line[i];
|
||||
if (c != ' ') lastPrintingCharIndex = i;
|
||||
}
|
||||
}
|
||||
if (lastPrintingCharIndex != -1) builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
|
||||
if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n');
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
for (int row = selY1; row <= selY2; row++) {
|
||||
int x1 = (row == selY1) ? selX1 : 0;
|
||||
int x2;
|
||||
if (row == selY2) {
|
||||
x2 = selX2 + 1;
|
||||
if (x2 > columns) x2 = columns;
|
||||
} else {
|
||||
x2 = columns;
|
||||
}
|
||||
TerminalRow lineObject = mLines[externalToInternalRow(row)];
|
||||
int x1Index = lineObject.findStartOfColumn(x1);
|
||||
int x2Index = (x2 < mColumns) ? lineObject.findStartOfColumn(x2) : lineObject.getSpaceUsed();
|
||||
if (x2Index == x1Index) {
|
||||
// Selected the start of a wide character.
|
||||
x2Index = lineObject.findStartOfColumn(x2 + 1);
|
||||
}
|
||||
char[] line = lineObject.mText;
|
||||
int lastPrintingCharIndex = -1;
|
||||
int i;
|
||||
boolean rowLineWrap = getLineWrap(row);
|
||||
if (rowLineWrap && x2 == columns) {
|
||||
// If the line was wrapped, we shouldn't lose trailing space:
|
||||
lastPrintingCharIndex = x2Index - 1;
|
||||
} else {
|
||||
for (i = x1Index; i < x2Index; ++i) {
|
||||
char c = line[i];
|
||||
if (c != ' ') lastPrintingCharIndex = i;
|
||||
}
|
||||
}
|
||||
if (lastPrintingCharIndex != -1)
|
||||
builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
|
||||
if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n');
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public int getActiveTranscriptRows() {
|
||||
return mActiveTranscriptRows;
|
||||
}
|
||||
public int getActiveTranscriptRows() {
|
||||
return mActiveTranscriptRows;
|
||||
}
|
||||
|
||||
public int getActiveRows() {
|
||||
return mActiveTranscriptRows + mScreenRows;
|
||||
}
|
||||
public int getActiveRows() {
|
||||
return mActiveTranscriptRows + mScreenRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a row value from the public external coordinate system to our internal private coordinate system.
|
||||
*
|
||||
* <ul>
|
||||
* <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
|
||||
* mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
|
||||
* </ul>
|
||||
*
|
||||
* External <---> Internal:
|
||||
*
|
||||
* <pre>
|
||||
* [ ... ] [ ... ]
|
||||
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
|
||||
* [ ... ] [ ... ]
|
||||
* [ 0 (visible screen starts here) ] <-----> [ mScreenFirstRow ]
|
||||
* [ ... ] [ ... ]
|
||||
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
|
||||
* </pre>
|
||||
*
|
||||
* @param externalRow
|
||||
* a row in the external coordinate system.
|
||||
* @return The row corresponding to the input argument in the private coordinate system.
|
||||
*/
|
||||
public int externalToInternalRow(int externalRow) {
|
||||
if (externalRow < -mActiveTranscriptRows || externalRow > mScreenRows)
|
||||
throw new IllegalArgumentException("extRow=" + externalRow + ", mScreenRows=" + mScreenRows + ", mActiveTranscriptRows=" + mActiveTranscriptRows);
|
||||
final int internalRow = mScreenFirstRow + externalRow;
|
||||
return (internalRow < 0) ? (mTotalRows + internalRow) : (internalRow % mTotalRows);
|
||||
}
|
||||
/**
|
||||
* Convert a row value from the public external coordinate system to our internal private coordinate system.
|
||||
* <p/>
|
||||
* <ul>
|
||||
* <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
|
||||
* mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
|
||||
* </ul>
|
||||
* <p/>
|
||||
* External <---> Internal:
|
||||
* <p/>
|
||||
* <pre>
|
||||
* [ ... ] [ ... ]
|
||||
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
|
||||
* [ ... ] [ ... ]
|
||||
* [ 0 (visible screen starts here) ] <-----> [ mScreenFirstRow ]
|
||||
* [ ... ] [ ... ]
|
||||
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
|
||||
* </pre>
|
||||
*
|
||||
* @param externalRow a row in the external coordinate system.
|
||||
* @return The row corresponding to the input argument in the private coordinate system.
|
||||
*/
|
||||
public int externalToInternalRow(int externalRow) {
|
||||
if (externalRow < -mActiveTranscriptRows || externalRow > mScreenRows)
|
||||
throw new IllegalArgumentException("extRow=" + externalRow + ", mScreenRows=" + mScreenRows + ", mActiveTranscriptRows=" + mActiveTranscriptRows);
|
||||
final int internalRow = mScreenFirstRow + externalRow;
|
||||
return (internalRow < 0) ? (mTotalRows + internalRow) : (internalRow % mTotalRows);
|
||||
}
|
||||
|
||||
public void setLineWrap(int row) {
|
||||
mLines[externalToInternalRow(row)].mLineWrap = true;
|
||||
}
|
||||
public void setLineWrap(int row) {
|
||||
mLines[externalToInternalRow(row)].mLineWrap = true;
|
||||
}
|
||||
|
||||
public boolean getLineWrap(int row) {
|
||||
return mLines[externalToInternalRow(row)].mLineWrap;
|
||||
}
|
||||
public boolean getLineWrap(int row) {
|
||||
return mLines[externalToInternalRow(row)].mLineWrap;
|
||||
}
|
||||
|
||||
public void clearLineWrap(int row) {
|
||||
mLines[externalToInternalRow(row)].mLineWrap = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the screen which this transcript backs. Currently, this only works if the number of columns does not
|
||||
* change or the rows expand (that is, it only works when shrinking the number of rows).
|
||||
*
|
||||
* @param newColumns
|
||||
* The number of columns the screen should have.
|
||||
* @param newRows
|
||||
* The number of rows the screen should have.
|
||||
* @param cursor
|
||||
* An int[2] containing the (column, row) cursor location.
|
||||
*/
|
||||
public void resize(int newColumns, int newRows, int newTotalRows, int[] cursor, int currentStyle, boolean altScreen) {
|
||||
// newRows > mTotalRows should not normally happen since mTotalRows is TRANSCRIPT_ROWS (10000):
|
||||
if (newColumns == mColumns && newRows <= mTotalRows) {
|
||||
// Fast resize where just the rows changed.
|
||||
int shiftDownOfTopRow = mScreenRows - newRows;
|
||||
if (shiftDownOfTopRow > 0 && shiftDownOfTopRow < mScreenRows) {
|
||||
// Shrinking. Check if we can skip blank rows at bottom below cursor.
|
||||
for (int i = mScreenRows - 1; i > 0; i--) {
|
||||
if (cursor[1] >= i) break;
|
||||
int r = externalToInternalRow(i);
|
||||
if (mLines[r] == null || mLines[r].isBlank()) {
|
||||
if (--shiftDownOfTopRow == 0) break;
|
||||
}
|
||||
}
|
||||
} else if (shiftDownOfTopRow < 0) {
|
||||
// Negative shift down = expanding. Only move screen up if there is transcript to show:
|
||||
int actualShift = Math.max(shiftDownOfTopRow, -mActiveTranscriptRows);
|
||||
if (shiftDownOfTopRow != actualShift) {
|
||||
// The new lines revealed by the resizing are not all from the transcript. Blank the below ones.
|
||||
for (int i = 0; i < actualShift - shiftDownOfTopRow; i++)
|
||||
allocateFullLineIfNecessary((mScreenFirstRow + mScreenRows + i) % mTotalRows).clear(currentStyle);
|
||||
shiftDownOfTopRow = actualShift;
|
||||
}
|
||||
}
|
||||
mScreenFirstRow += shiftDownOfTopRow;
|
||||
mScreenFirstRow = (mScreenFirstRow < 0) ? (mScreenFirstRow + mTotalRows) : (mScreenFirstRow % mTotalRows);
|
||||
mTotalRows = newTotalRows;
|
||||
mActiveTranscriptRows = altScreen ? 0 : Math.max(0, mActiveTranscriptRows + shiftDownOfTopRow);
|
||||
cursor[1] -= shiftDownOfTopRow;
|
||||
mScreenRows = newRows;
|
||||
} else {
|
||||
// Copy away old state and update new:
|
||||
TerminalRow[] oldLines = mLines;
|
||||
mLines = new TerminalRow[newTotalRows];
|
||||
for (int i = 0; i < newTotalRows; i++)
|
||||
mLines[i] = new TerminalRow(newColumns, currentStyle);
|
||||
/**
|
||||
* Resize the screen which this transcript backs. Currently, this only works if the number of columns does not
|
||||
* change or the rows expand (that is, it only works when shrinking the number of rows).
|
||||
*
|
||||
* @param newColumns The number of columns the screen should have.
|
||||
* @param newRows The number of rows the screen should have.
|
||||
* @param cursor An int[2] containing the (column, row) cursor location.
|
||||
*/
|
||||
public void resize(int newColumns, int newRows, int newTotalRows, int[] cursor, int currentStyle, boolean altScreen) {
|
||||
// newRows > mTotalRows should not normally happen since mTotalRows is TRANSCRIPT_ROWS (10000):
|
||||
if (newColumns == mColumns && newRows <= mTotalRows) {
|
||||
// Fast resize where just the rows changed.
|
||||
int shiftDownOfTopRow = mScreenRows - newRows;
|
||||
if (shiftDownOfTopRow > 0 && shiftDownOfTopRow < mScreenRows) {
|
||||
// Shrinking. Check if we can skip blank rows at bottom below cursor.
|
||||
for (int i = mScreenRows - 1; i > 0; i--) {
|
||||
if (cursor[1] >= i) break;
|
||||
int r = externalToInternalRow(i);
|
||||
if (mLines[r] == null || mLines[r].isBlank()) {
|
||||
if (--shiftDownOfTopRow == 0) break;
|
||||
}
|
||||
}
|
||||
} else if (shiftDownOfTopRow < 0) {
|
||||
// Negative shift down = expanding. Only move screen up if there is transcript to show:
|
||||
int actualShift = Math.max(shiftDownOfTopRow, -mActiveTranscriptRows);
|
||||
if (shiftDownOfTopRow != actualShift) {
|
||||
// The new lines revealed by the resizing are not all from the transcript. Blank the below ones.
|
||||
for (int i = 0; i < actualShift - shiftDownOfTopRow; i++)
|
||||
allocateFullLineIfNecessary((mScreenFirstRow + mScreenRows + i) % mTotalRows).clear(currentStyle);
|
||||
shiftDownOfTopRow = actualShift;
|
||||
}
|
||||
}
|
||||
mScreenFirstRow += shiftDownOfTopRow;
|
||||
mScreenFirstRow = (mScreenFirstRow < 0) ? (mScreenFirstRow + mTotalRows) : (mScreenFirstRow % mTotalRows);
|
||||
mTotalRows = newTotalRows;
|
||||
mActiveTranscriptRows = altScreen ? 0 : Math.max(0, mActiveTranscriptRows + shiftDownOfTopRow);
|
||||
cursor[1] -= shiftDownOfTopRow;
|
||||
mScreenRows = newRows;
|
||||
} else {
|
||||
// Copy away old state and update new:
|
||||
TerminalRow[] oldLines = mLines;
|
||||
mLines = new TerminalRow[newTotalRows];
|
||||
for (int i = 0; i < newTotalRows; i++)
|
||||
mLines[i] = new TerminalRow(newColumns, currentStyle);
|
||||
|
||||
final int oldActiveTranscriptRows = mActiveTranscriptRows;
|
||||
final int oldScreenFirstRow = mScreenFirstRow;
|
||||
final int oldScreenRows = mScreenRows;
|
||||
final int oldTotalRows = mTotalRows;
|
||||
mTotalRows = newTotalRows;
|
||||
mScreenRows = newRows;
|
||||
mActiveTranscriptRows = mScreenFirstRow = 0;
|
||||
mColumns = newColumns;
|
||||
final int oldActiveTranscriptRows = mActiveTranscriptRows;
|
||||
final int oldScreenFirstRow = mScreenFirstRow;
|
||||
final int oldScreenRows = mScreenRows;
|
||||
final int oldTotalRows = mTotalRows;
|
||||
mTotalRows = newTotalRows;
|
||||
mScreenRows = newRows;
|
||||
mActiveTranscriptRows = mScreenFirstRow = 0;
|
||||
mColumns = newColumns;
|
||||
|
||||
int newCursorRow = -1;
|
||||
int newCursorColumn = -1;
|
||||
int oldCursorRow = cursor[1];
|
||||
int oldCursorColumn = cursor[0];
|
||||
boolean newCursorPlaced = false;
|
||||
int newCursorRow = -1;
|
||||
int newCursorColumn = -1;
|
||||
int oldCursorRow = cursor[1];
|
||||
int oldCursorColumn = cursor[0];
|
||||
boolean newCursorPlaced = false;
|
||||
|
||||
int currentOutputExternalRow = 0;
|
||||
int currentOutputExternalColumn = 0;
|
||||
int currentOutputExternalRow = 0;
|
||||
int currentOutputExternalColumn = 0;
|
||||
|
||||
// Loop over every character in the initial state.
|
||||
// Blank lines should be skipped only if at end of transcript (just as is done in the "fast" resize), so we
|
||||
// keep track how many blank lines we have skipped if we later on find a non-blank line.
|
||||
int skippedBlankLines = 0;
|
||||
for (int externalOldRow = -oldActiveTranscriptRows; externalOldRow < oldScreenRows; externalOldRow++) {
|
||||
// Do what externalToInternalRow() does but for the old state:
|
||||
int internalOldRow = oldScreenFirstRow + externalOldRow;
|
||||
internalOldRow = (internalOldRow < 0) ? (oldTotalRows + internalOldRow) : (internalOldRow % oldTotalRows);
|
||||
// Loop over every character in the initial state.
|
||||
// Blank lines should be skipped only if at end of transcript (just as is done in the "fast" resize), so we
|
||||
// keep track how many blank lines we have skipped if we later on find a non-blank line.
|
||||
int skippedBlankLines = 0;
|
||||
for (int externalOldRow = -oldActiveTranscriptRows; externalOldRow < oldScreenRows; externalOldRow++) {
|
||||
// Do what externalToInternalRow() does but for the old state:
|
||||
int internalOldRow = oldScreenFirstRow + externalOldRow;
|
||||
internalOldRow = (internalOldRow < 0) ? (oldTotalRows + internalOldRow) : (internalOldRow % oldTotalRows);
|
||||
|
||||
TerminalRow oldLine = oldLines[internalOldRow];
|
||||
boolean cursorAtThisRow = externalOldRow == oldCursorRow;
|
||||
// The cursor may only be on a non-null line, which we should not skip:
|
||||
if (oldLine == null || (!(!newCursorPlaced && cursorAtThisRow)) && oldLine.isBlank()) {
|
||||
skippedBlankLines++;
|
||||
continue;
|
||||
} else if (skippedBlankLines > 0) {
|
||||
// After skipping some blank lines we encounter a non-blank line. Insert the skipped blank lines.
|
||||
for (int i = 0; i < skippedBlankLines; i++) {
|
||||
if (currentOutputExternalRow == mScreenRows - 1) {
|
||||
scrollDownOneLine(0, mScreenRows, currentStyle);
|
||||
} else {
|
||||
currentOutputExternalRow++;
|
||||
}
|
||||
currentOutputExternalColumn = 0;
|
||||
}
|
||||
skippedBlankLines = 0;
|
||||
}
|
||||
TerminalRow oldLine = oldLines[internalOldRow];
|
||||
boolean cursorAtThisRow = externalOldRow == oldCursorRow;
|
||||
// The cursor may only be on a non-null line, which we should not skip:
|
||||
if (oldLine == null || (!(!newCursorPlaced && cursorAtThisRow)) && oldLine.isBlank()) {
|
||||
skippedBlankLines++;
|
||||
continue;
|
||||
} else if (skippedBlankLines > 0) {
|
||||
// After skipping some blank lines we encounter a non-blank line. Insert the skipped blank lines.
|
||||
for (int i = 0; i < skippedBlankLines; i++) {
|
||||
if (currentOutputExternalRow == mScreenRows - 1) {
|
||||
scrollDownOneLine(0, mScreenRows, currentStyle);
|
||||
} else {
|
||||
currentOutputExternalRow++;
|
||||
}
|
||||
currentOutputExternalColumn = 0;
|
||||
}
|
||||
skippedBlankLines = 0;
|
||||
}
|
||||
|
||||
int lastNonSpaceIndex = 0;
|
||||
boolean justToCursor = false;
|
||||
if (cursorAtThisRow || oldLine.mLineWrap) {
|
||||
// Take the whole line, either because of cursor on it, or if line wrapping.
|
||||
lastNonSpaceIndex = oldLine.getSpaceUsed();
|
||||
if (cursorAtThisRow) justToCursor = true;
|
||||
} else {
|
||||
for (int i = 0; i < oldLine.getSpaceUsed(); i++)
|
||||
// NEWLY INTRODUCED BUG! Should not index oldLine.mStyle with char indices
|
||||
if (oldLine.mText[i] != ' '/* || oldLine.mStyle[i] != currentStyle */) lastNonSpaceIndex = i + 1;
|
||||
}
|
||||
int lastNonSpaceIndex = 0;
|
||||
boolean justToCursor = false;
|
||||
if (cursorAtThisRow || oldLine.mLineWrap) {
|
||||
// Take the whole line, either because of cursor on it, or if line wrapping.
|
||||
lastNonSpaceIndex = oldLine.getSpaceUsed();
|
||||
if (cursorAtThisRow) justToCursor = true;
|
||||
} else {
|
||||
for (int i = 0; i < oldLine.getSpaceUsed(); i++)
|
||||
// NEWLY INTRODUCED BUG! Should not index oldLine.mStyle with char indices
|
||||
if (oldLine.mText[i] != ' '/* || oldLine.mStyle[i] != currentStyle */)
|
||||
lastNonSpaceIndex = i + 1;
|
||||
}
|
||||
|
||||
int currentOldCol = 0;
|
||||
int styleAtCol = 0;
|
||||
for (int i = 0; i < lastNonSpaceIndex; i++) {
|
||||
// Note that looping over java character, not cells.
|
||||
char c = oldLine.mText[i];
|
||||
int codePoint = (Character.isHighSurrogate(c)) ? Character.toCodePoint(c, oldLine.mText[++i]) : c;
|
||||
int displayWidth = WcWidth.width(codePoint);
|
||||
// Use the last style if this is a zero-width character:
|
||||
if (displayWidth > 0) styleAtCol = oldLine.getStyle(currentOldCol);
|
||||
int currentOldCol = 0;
|
||||
int styleAtCol = 0;
|
||||
for (int i = 0; i < lastNonSpaceIndex; i++) {
|
||||
// Note that looping over java character, not cells.
|
||||
char c = oldLine.mText[i];
|
||||
int codePoint = (Character.isHighSurrogate(c)) ? Character.toCodePoint(c, oldLine.mText[++i]) : c;
|
||||
int displayWidth = WcWidth.width(codePoint);
|
||||
// Use the last style if this is a zero-width character:
|
||||
if (displayWidth > 0) styleAtCol = oldLine.getStyle(currentOldCol);
|
||||
|
||||
// Line wrap as necessary:
|
||||
if (currentOutputExternalColumn + displayWidth > mColumns) {
|
||||
setLineWrap(currentOutputExternalRow);
|
||||
if (currentOutputExternalRow == mScreenRows - 1) {
|
||||
if (newCursorPlaced) newCursorRow--;
|
||||
scrollDownOneLine(0, mScreenRows, currentStyle);
|
||||
} else {
|
||||
currentOutputExternalRow++;
|
||||
}
|
||||
currentOutputExternalColumn = 0;
|
||||
}
|
||||
// Line wrap as necessary:
|
||||
if (currentOutputExternalColumn + displayWidth > mColumns) {
|
||||
setLineWrap(currentOutputExternalRow);
|
||||
if (currentOutputExternalRow == mScreenRows - 1) {
|
||||
if (newCursorPlaced) newCursorRow--;
|
||||
scrollDownOneLine(0, mScreenRows, currentStyle);
|
||||
} else {
|
||||
currentOutputExternalRow++;
|
||||
}
|
||||
currentOutputExternalColumn = 0;
|
||||
}
|
||||
|
||||
int offsetDueToCombiningChar = ((displayWidth <= 0 && currentOutputExternalColumn > 0) ? 1 : 0);
|
||||
int outputColumn = currentOutputExternalColumn - offsetDueToCombiningChar;
|
||||
setChar(outputColumn, currentOutputExternalRow, codePoint, styleAtCol);
|
||||
int offsetDueToCombiningChar = ((displayWidth <= 0 && currentOutputExternalColumn > 0) ? 1 : 0);
|
||||
int outputColumn = currentOutputExternalColumn - offsetDueToCombiningChar;
|
||||
setChar(outputColumn, currentOutputExternalRow, codePoint, styleAtCol);
|
||||
|
||||
if (displayWidth > 0) {
|
||||
if (oldCursorRow == externalOldRow && oldCursorColumn == currentOldCol) {
|
||||
newCursorColumn = currentOutputExternalColumn;
|
||||
newCursorRow = currentOutputExternalRow;
|
||||
newCursorPlaced = true;
|
||||
}
|
||||
currentOldCol += displayWidth;
|
||||
currentOutputExternalColumn += displayWidth;
|
||||
if (justToCursor && newCursorPlaced) break;
|
||||
}
|
||||
}
|
||||
// Old row has been copied. Check if we need to insert newline if old line was not wrapping:
|
||||
if (externalOldRow != (oldScreenRows - 1) && !oldLine.mLineWrap) {
|
||||
if (currentOutputExternalRow == mScreenRows - 1) {
|
||||
if (newCursorPlaced) newCursorRow--;
|
||||
scrollDownOneLine(0, mScreenRows, currentStyle);
|
||||
} else {
|
||||
currentOutputExternalRow++;
|
||||
}
|
||||
currentOutputExternalColumn = 0;
|
||||
}
|
||||
}
|
||||
if (displayWidth > 0) {
|
||||
if (oldCursorRow == externalOldRow && oldCursorColumn == currentOldCol) {
|
||||
newCursorColumn = currentOutputExternalColumn;
|
||||
newCursorRow = currentOutputExternalRow;
|
||||
newCursorPlaced = true;
|
||||
}
|
||||
currentOldCol += displayWidth;
|
||||
currentOutputExternalColumn += displayWidth;
|
||||
if (justToCursor && newCursorPlaced) break;
|
||||
}
|
||||
}
|
||||
// Old row has been copied. Check if we need to insert newline if old line was not wrapping:
|
||||
if (externalOldRow != (oldScreenRows - 1) && !oldLine.mLineWrap) {
|
||||
if (currentOutputExternalRow == mScreenRows - 1) {
|
||||
if (newCursorPlaced) newCursorRow--;
|
||||
scrollDownOneLine(0, mScreenRows, currentStyle);
|
||||
} else {
|
||||
currentOutputExternalRow++;
|
||||
}
|
||||
currentOutputExternalColumn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
cursor[0] = newCursorColumn;
|
||||
cursor[1] = newCursorRow;
|
||||
}
|
||||
cursor[0] = newCursorColumn;
|
||||
cursor[1] = newCursorRow;
|
||||
}
|
||||
|
||||
// Handle cursor scrolling off screen:
|
||||
if (cursor[0] < 0 || cursor[1] < 0) cursor[0] = cursor[1] = 0;
|
||||
}
|
||||
// Handle cursor scrolling off screen:
|
||||
if (cursor[0] < 0 || cursor[1] < 0) cursor[0] = cursor[1] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block copy lines and associated metadata from one location to another in the circular buffer, taking wraparound
|
||||
* into account.
|
||||
*
|
||||
* @param srcInternal
|
||||
* The first line to be copied.
|
||||
* @param len
|
||||
* The number of lines to be copied.
|
||||
*/
|
||||
private void blockCopyLinesDown(int srcInternal, int len) {
|
||||
if (len == 0) return;
|
||||
int totalRows = mTotalRows;
|
||||
/**
|
||||
* Block copy lines and associated metadata from one location to another in the circular buffer, taking wraparound
|
||||
* into account.
|
||||
*
|
||||
* @param srcInternal The first line to be copied.
|
||||
* @param len The number of lines to be copied.
|
||||
*/
|
||||
private void blockCopyLinesDown(int srcInternal, int len) {
|
||||
if (len == 0) return;
|
||||
int totalRows = mTotalRows;
|
||||
|
||||
int start = len - 1;
|
||||
// Save away line to be overwritten:
|
||||
TerminalRow lineToBeOverWritten = mLines[(srcInternal + start + 1) % totalRows];
|
||||
// Do the copy from bottom to top.
|
||||
for (int i = start; i >= 0; --i)
|
||||
mLines[(srcInternal + i + 1) % totalRows] = mLines[(srcInternal + i) % totalRows];
|
||||
// Put back overwritten line, now above the block:
|
||||
mLines[(srcInternal) % totalRows] = lineToBeOverWritten;
|
||||
}
|
||||
int start = len - 1;
|
||||
// Save away line to be overwritten:
|
||||
TerminalRow lineToBeOverWritten = mLines[(srcInternal + start + 1) % totalRows];
|
||||
// Do the copy from bottom to top.
|
||||
for (int i = start; i >= 0; --i)
|
||||
mLines[(srcInternal + i + 1) % totalRows] = mLines[(srcInternal + i) % totalRows];
|
||||
// Put back overwritten line, now above the block:
|
||||
mLines[(srcInternal) % totalRows] = lineToBeOverWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the screen down one line. To scroll the whole screen of a 24 line screen, the arguments would be (0, 24).
|
||||
*
|
||||
* @param topMargin
|
||||
* First line that is scrolled.
|
||||
* @param bottomMargin
|
||||
* One line after the last line that is scrolled.
|
||||
* @param style
|
||||
* the style for the newly exposed line.
|
||||
*/
|
||||
public void scrollDownOneLine(int topMargin, int bottomMargin, int style) {
|
||||
if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows)
|
||||
throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + mScreenRows);
|
||||
/**
|
||||
* Scroll the screen down one line. To scroll the whole screen of a 24 line screen, the arguments would be (0, 24).
|
||||
*
|
||||
* @param topMargin First line that is scrolled.
|
||||
* @param bottomMargin One line after the last line that is scrolled.
|
||||
* @param style the style for the newly exposed line.
|
||||
*/
|
||||
public void scrollDownOneLine(int topMargin, int bottomMargin, int style) {
|
||||
if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows)
|
||||
throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + mScreenRows);
|
||||
|
||||
// Copy the fixed topMargin lines one line down so that they remain on screen in same position:
|
||||
blockCopyLinesDown(mScreenFirstRow, topMargin);
|
||||
// Copy the fixed mScreenRows-bottomMargin lines one line down so that they remain on screen in same
|
||||
// position:
|
||||
blockCopyLinesDown(externalToInternalRow(bottomMargin), mScreenRows - bottomMargin);
|
||||
// Copy the fixed topMargin lines one line down so that they remain on screen in same position:
|
||||
blockCopyLinesDown(mScreenFirstRow, topMargin);
|
||||
// Copy the fixed mScreenRows-bottomMargin lines one line down so that they remain on screen in same
|
||||
// position:
|
||||
blockCopyLinesDown(externalToInternalRow(bottomMargin), mScreenRows - bottomMargin);
|
||||
|
||||
// Update the screen location in the ring buffer:
|
||||
mScreenFirstRow = (mScreenFirstRow + 1) % mTotalRows;
|
||||
// Note that the history has grown if not already full:
|
||||
if (mActiveTranscriptRows < mTotalRows - mScreenRows) mActiveTranscriptRows++;
|
||||
// Update the screen location in the ring buffer:
|
||||
mScreenFirstRow = (mScreenFirstRow + 1) % mTotalRows;
|
||||
// Note that the history has grown if not already full:
|
||||
if (mActiveTranscriptRows < mTotalRows - mScreenRows) mActiveTranscriptRows++;
|
||||
|
||||
// Blank the newly revealed line above the bottom margin:
|
||||
int blankRow = externalToInternalRow(bottomMargin - 1);
|
||||
if (mLines[blankRow] == null) {
|
||||
mLines[blankRow] = new TerminalRow(mColumns, style);
|
||||
} else {
|
||||
mLines[blankRow].clear(style);
|
||||
}
|
||||
}
|
||||
// Blank the newly revealed line above the bottom margin:
|
||||
int blankRow = externalToInternalRow(bottomMargin - 1);
|
||||
if (mLines[blankRow] == null) {
|
||||
mLines[blankRow] = new TerminalRow(mColumns, style);
|
||||
} else {
|
||||
mLines[blankRow].clear(style);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Block copy characters from one position in the screen to another. The two positions can overlap. All characters
|
||||
* of the source and destination must be within the bounds of the screen, or else an InvalidParameterException will
|
||||
* be thrown.
|
||||
*
|
||||
* @param sx
|
||||
* source X coordinate
|
||||
* @param sy
|
||||
* source Y coordinate
|
||||
* @param w
|
||||
* width
|
||||
* @param h
|
||||
* height
|
||||
* @param dx
|
||||
* destination X coordinate
|
||||
* @param dy
|
||||
* destination Y coordinate
|
||||
*/
|
||||
public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
|
||||
if (w == 0) return;
|
||||
if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows || dx < 0 || dx + w > mColumns || dy < 0 || dy + h > mScreenRows)
|
||||
throw new IllegalArgumentException();
|
||||
boolean copyingUp = sy > dy;
|
||||
for (int y = 0; y < h; y++) {
|
||||
int y2 = copyingUp ? y : (h - (y + 1));
|
||||
TerminalRow sourceRow = allocateFullLineIfNecessary(externalToInternalRow(sy + y2));
|
||||
allocateFullLineIfNecessary(externalToInternalRow(dy + y2)).copyInterval(sourceRow, sx, sx + w, dx);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Block copy characters from one position in the screen to another. The two positions can overlap. All characters
|
||||
* of the source and destination must be within the bounds of the screen, or else an InvalidParameterException will
|
||||
* be thrown.
|
||||
*
|
||||
* @param sx source X coordinate
|
||||
* @param sy source Y coordinate
|
||||
* @param w width
|
||||
* @param h height
|
||||
* @param dx destination X coordinate
|
||||
* @param dy destination Y coordinate
|
||||
*/
|
||||
public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
|
||||
if (w == 0) return;
|
||||
if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows || dx < 0 || dx + w > mColumns || dy < 0 || dy + h > mScreenRows)
|
||||
throw new IllegalArgumentException();
|
||||
boolean copyingUp = sy > dy;
|
||||
for (int y = 0; y < h; y++) {
|
||||
int y2 = copyingUp ? y : (h - (y + 1));
|
||||
TerminalRow sourceRow = allocateFullLineIfNecessary(externalToInternalRow(sy + y2));
|
||||
allocateFullLineIfNecessary(externalToInternalRow(dy + y2)).copyInterval(sourceRow, sx, sx + w, dx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Block set characters. All characters must be within the bounds of the screen, or else and
|
||||
* InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block
|
||||
* of characters.
|
||||
*/
|
||||
public void blockSet(int sx, int sy, int w, int h, int val, int style) {
|
||||
if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
|
||||
throw new IllegalArgumentException(
|
||||
"Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")");
|
||||
}
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++)
|
||||
setChar(sx + x, sy + y, val, style);
|
||||
}
|
||||
/**
|
||||
* Block set characters. All characters must be within the bounds of the screen, or else and
|
||||
* InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block
|
||||
* of characters.
|
||||
*/
|
||||
public void blockSet(int sx, int sy, int w, int h, int val, int style) {
|
||||
if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
|
||||
throw new IllegalArgumentException(
|
||||
"Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")");
|
||||
}
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++)
|
||||
setChar(sx + x, sy + y, val, style);
|
||||
}
|
||||
|
||||
public TerminalRow allocateFullLineIfNecessary(int row) {
|
||||
return (mLines[row] == null) ? (mLines[row] = new TerminalRow(mColumns, 0)) : mLines[row];
|
||||
}
|
||||
public TerminalRow allocateFullLineIfNecessary(int row) {
|
||||
return (mLines[row] == null) ? (mLines[row] = new TerminalRow(mColumns, 0)) : mLines[row];
|
||||
}
|
||||
|
||||
public void setChar(int column, int row, int codePoint, int style) {
|
||||
if (row >= mScreenRows || column >= mColumns)
|
||||
throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
|
||||
row = externalToInternalRow(row);
|
||||
allocateFullLineIfNecessary(row).setChar(column, codePoint, style);
|
||||
}
|
||||
public void setChar(int column, int row, int codePoint, int style) {
|
||||
if (row >= mScreenRows || column >= mColumns)
|
||||
throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
|
||||
row = externalToInternalRow(row);
|
||||
allocateFullLineIfNecessary(row).setChar(column, codePoint, style);
|
||||
}
|
||||
|
||||
public int getStyleAt(int externalRow, int column) {
|
||||
return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column);
|
||||
}
|
||||
public int getStyleAt(int externalRow, int column) {
|
||||
return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column);
|
||||
}
|
||||
|
||||
/** Support for http://vt100.net/docs/vt510-rm/DECCARA and http://vt100.net/docs/vt510-rm/DECCARA */
|
||||
public void setOrClearEffect(int bits, boolean setOrClear, boolean reverse, boolean rectangular, int leftMargin, int rightMargin, int top, int left,
|
||||
int bottom, int right) {
|
||||
for (int y = top; y < bottom; y++) {
|
||||
TerminalRow line = mLines[externalToInternalRow(y)];
|
||||
int startOfLine = (rectangular || y == top) ? left : leftMargin;
|
||||
int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin;
|
||||
for (int x = startOfLine; x < endOfLine; x++) {
|
||||
int currentStyle = line.getStyle(x);
|
||||
int foreColor = TextStyle.decodeForeColor(currentStyle);
|
||||
int backColor = TextStyle.decodeBackColor(currentStyle);
|
||||
int effect = TextStyle.decodeEffect(currentStyle);
|
||||
if (reverse) {
|
||||
// Clear out the bits to reverse and add them back in reversed:
|
||||
effect = (effect & ~bits) | (bits & ~effect);
|
||||
} else if (setOrClear) {
|
||||
effect |= bits;
|
||||
} else {
|
||||
effect &= ~bits;
|
||||
}
|
||||
line.mStyle[x] = TextStyle.encode(foreColor, backColor, effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Support for http://vt100.net/docs/vt510-rm/DECCARA and http://vt100.net/docs/vt510-rm/DECCARA */
|
||||
public void setOrClearEffect(int bits, boolean setOrClear, boolean reverse, boolean rectangular, int leftMargin, int rightMargin, int top, int left,
|
||||
int bottom, int right) {
|
||||
for (int y = top; y < bottom; y++) {
|
||||
TerminalRow line = mLines[externalToInternalRow(y)];
|
||||
int startOfLine = (rectangular || y == top) ? left : leftMargin;
|
||||
int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin;
|
||||
for (int x = startOfLine; x < endOfLine; x++) {
|
||||
int currentStyle = line.getStyle(x);
|
||||
int foreColor = TextStyle.decodeForeColor(currentStyle);
|
||||
int backColor = TextStyle.decodeBackColor(currentStyle);
|
||||
int effect = TextStyle.decodeEffect(currentStyle);
|
||||
if (reverse) {
|
||||
// Clear out the bits to reverse and add them back in reversed:
|
||||
effect = (effect & ~bits) | (bits & ~effect);
|
||||
} else if (setOrClear) {
|
||||
effect |= bits;
|
||||
} else {
|
||||
effect &= ~bits;
|
||||
}
|
||||
line.mStyle[x] = TextStyle.encode(foreColor, backColor, effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,97 +6,98 @@ import java.util.Properties;
|
|||
/**
|
||||
* Color scheme for a terminal with default colors, which may be overridden (and then reset) from the shell using
|
||||
* Operating System Control (OSC) sequences.
|
||||
*
|
||||
*
|
||||
* @see TerminalColors
|
||||
*/
|
||||
public final class TerminalColorScheme {
|
||||
|
||||
/** http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg, but with blue color brighter. */
|
||||
private static final int[] DEFAULT_COLORSCHEME = {
|
||||
// 16 original colors. First 8 are dim.
|
||||
0xff000000, // black
|
||||
0xffcd0000, // dim red
|
||||
0xff00cd00, // dim green
|
||||
0xffcdcd00, // dim yellow
|
||||
0xff6495ed, // dim blue
|
||||
0xffcd00cd, // dim magenta
|
||||
0xff00cdcd, // dim cyan
|
||||
0xffe5e5e5, // dim white
|
||||
// Second 8 are bright:
|
||||
0xff7f7f7f, // medium grey
|
||||
0xffff0000, // bright red
|
||||
0xff00ff00, // bright green
|
||||
0xffffff00, // bright yellow
|
||||
0xff5c5cff, // light blue
|
||||
0xffff00ff, // bright magenta
|
||||
0xff00ffff, // bright cyan
|
||||
0xffffffff, // bright white
|
||||
/** http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg, but with blue color brighter. */
|
||||
private static final int[] DEFAULT_COLORSCHEME = {
|
||||
// 16 original colors. First 8 are dim.
|
||||
0xff000000, // black
|
||||
0xffcd0000, // dim red
|
||||
0xff00cd00, // dim green
|
||||
0xffcdcd00, // dim yellow
|
||||
0xff6495ed, // dim blue
|
||||
0xffcd00cd, // dim magenta
|
||||
0xff00cdcd, // dim cyan
|
||||
0xffe5e5e5, // dim white
|
||||
// Second 8 are bright:
|
||||
0xff7f7f7f, // medium grey
|
||||
0xffff0000, // bright red
|
||||
0xff00ff00, // bright green
|
||||
0xffffff00, // bright yellow
|
||||
0xff5c5cff, // light blue
|
||||
0xffff00ff, // bright magenta
|
||||
0xff00ffff, // bright cyan
|
||||
0xffffffff, // bright white
|
||||
|
||||
// 216 color cube, six shades of each color:
|
||||
0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, 0xff005fd7, 0xff005fff,
|
||||
0xff008700, 0xff00875f, 0xff008787, 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff,
|
||||
0xff00d700, 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, 0xff00ffff,
|
||||
0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff,
|
||||
0xff5f8700, 0xff5f875f, 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff,
|
||||
0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, 0xff5fffd7, 0xff5fffff,
|
||||
0xff870000, 0xff87005f, 0xff870087, 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff,
|
||||
0xff878700, 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, 0xff87afff,
|
||||
0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, 0xff87ffaf, 0xff87ffd7, 0xff87ffff,
|
||||
0xffaf0000, 0xffaf005f, 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff,
|
||||
0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, 0xffafafd7, 0xffafafff,
|
||||
0xffafd700, 0xffafd75f, 0xffafd787, 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff,
|
||||
0xffd70000, 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, 0xffd75fff,
|
||||
0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, 0xffd7afaf, 0xffd7afd7, 0xffd7afff,
|
||||
0xffd7d700, 0xffd7d75f, 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff,
|
||||
0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, 0xffff5fd7, 0xffff5fff,
|
||||
0xffff8700, 0xffff875f, 0xffff8787, 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff,
|
||||
0xffffd700, 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, 0xffffffff,
|
||||
// 216 color cube, six shades of each color:
|
||||
0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, 0xff005fd7, 0xff005fff,
|
||||
0xff008700, 0xff00875f, 0xff008787, 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff,
|
||||
0xff00d700, 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, 0xff00ffff,
|
||||
0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff,
|
||||
0xff5f8700, 0xff5f875f, 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff,
|
||||
0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, 0xff5fffd7, 0xff5fffff,
|
||||
0xff870000, 0xff87005f, 0xff870087, 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff,
|
||||
0xff878700, 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, 0xff87afff,
|
||||
0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, 0xff87ffaf, 0xff87ffd7, 0xff87ffff,
|
||||
0xffaf0000, 0xffaf005f, 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff,
|
||||
0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, 0xffafafd7, 0xffafafff,
|
||||
0xffafd700, 0xffafd75f, 0xffafd787, 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff,
|
||||
0xffd70000, 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, 0xffd75fff,
|
||||
0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, 0xffd7afaf, 0xffd7afd7, 0xffd7afff,
|
||||
0xffd7d700, 0xffd7d75f, 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff,
|
||||
0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, 0xffff5fd7, 0xffff5fff,
|
||||
0xffff8700, 0xffff875f, 0xffff8787, 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff,
|
||||
0xffffd700, 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, 0xffffffff,
|
||||
|
||||
// 24 grey scale ramp:
|
||||
0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff626262, 0xff6c6c6c, 0xff767676,
|
||||
0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee,
|
||||
// 24 grey scale ramp:
|
||||
0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff626262, 0xff6c6c6c, 0xff767676,
|
||||
0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee,
|
||||
|
||||
// COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR:
|
||||
0xffffffff, 0xff000000, 0xffffffff };
|
||||
// COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR:
|
||||
0xffffffff, 0xff000000, 0xffffffff};
|
||||
|
||||
public final int[] mDefaultColors = new int[TextStyle.NUM_INDEXED_COLORS];
|
||||
public final int[] mDefaultColors = new int[TextStyle.NUM_INDEXED_COLORS];
|
||||
|
||||
public TerminalColorScheme() {
|
||||
reset();
|
||||
}
|
||||
public TerminalColorScheme() {
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
System.arraycopy(DEFAULT_COLORSCHEME, 0, mDefaultColors, 0, TextStyle.NUM_INDEXED_COLORS);
|
||||
}
|
||||
private void reset() {
|
||||
System.arraycopy(DEFAULT_COLORSCHEME, 0, mDefaultColors, 0, TextStyle.NUM_INDEXED_COLORS);
|
||||
}
|
||||
|
||||
public void updateWith(Properties props) {
|
||||
reset();
|
||||
for (Map.Entry<Object, Object> entries : props.entrySet()) {
|
||||
String key = (String) entries.getKey();
|
||||
String value = (String) entries.getValue();
|
||||
int colorIndex;
|
||||
public void updateWith(Properties props) {
|
||||
reset();
|
||||
for (Map.Entry<Object, Object> entries : props.entrySet()) {
|
||||
String key = (String) entries.getKey();
|
||||
String value = (String) entries.getValue();
|
||||
int colorIndex;
|
||||
|
||||
if (key.equals("foreground")) {
|
||||
colorIndex = TextStyle.COLOR_INDEX_FOREGROUND;
|
||||
} else if (key.equals("background")) {
|
||||
colorIndex = TextStyle.COLOR_INDEX_BACKGROUND;
|
||||
} else if (key.equals("cursor")) {
|
||||
colorIndex = TextStyle.COLOR_INDEX_CURSOR;
|
||||
} else if (key.startsWith("color")) {
|
||||
try {
|
||||
colorIndex = Integer.parseInt(key.substring(5));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid property: '" + key + "'");
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid property: '" + key + "'");
|
||||
}
|
||||
if (key.equals("foreground")) {
|
||||
colorIndex = TextStyle.COLOR_INDEX_FOREGROUND;
|
||||
} else if (key.equals("background")) {
|
||||
colorIndex = TextStyle.COLOR_INDEX_BACKGROUND;
|
||||
} else if (key.equals("cursor")) {
|
||||
colorIndex = TextStyle.COLOR_INDEX_CURSOR;
|
||||
} else if (key.startsWith("color")) {
|
||||
try {
|
||||
colorIndex = Integer.parseInt(key.substring(5));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid property: '" + key + "'");
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid property: '" + key + "'");
|
||||
}
|
||||
|
||||
int colorValue = TerminalColors.parse(value);
|
||||
if (colorValue == 0) throw new IllegalArgumentException("Property '" + key + "' has invalid color: '" + value + "'");
|
||||
int colorValue = TerminalColors.parse(value);
|
||||
if (colorValue == 0)
|
||||
throw new IllegalArgumentException("Property '" + key + "' has invalid color: '" + value + "'");
|
||||
|
||||
mDefaultColors[colorIndex] = colorValue;
|
||||
}
|
||||
}
|
||||
mDefaultColors[colorIndex] = colorValue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,74 +3,74 @@ package com.termux.terminal;
|
|||
/** Current terminal colors (if different from default). */
|
||||
public final class TerminalColors {
|
||||
|
||||
/** Static data - a bit ugly but ok for now. */
|
||||
public static final TerminalColorScheme COLOR_SCHEME = new TerminalColorScheme();
|
||||
/** Static data - a bit ugly but ok for now. */
|
||||
public static final TerminalColorScheme COLOR_SCHEME = new TerminalColorScheme();
|
||||
|
||||
/**
|
||||
* The current terminal colors, which are normally set from the color theme, but may be set dynamically with the OSC
|
||||
* 4 control sequence.
|
||||
*/
|
||||
public final int[] mCurrentColors = new int[TextStyle.NUM_INDEXED_COLORS];
|
||||
/**
|
||||
* The current terminal colors, which are normally set from the color theme, but may be set dynamically with the OSC
|
||||
* 4 control sequence.
|
||||
*/
|
||||
public final int[] mCurrentColors = new int[TextStyle.NUM_INDEXED_COLORS];
|
||||
|
||||
/** Create a new instance with default colors from the theme. */
|
||||
public TerminalColors() {
|
||||
reset();
|
||||
}
|
||||
/** Create a new instance with default colors from the theme. */
|
||||
public TerminalColors() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/** Reset a particular indexed color with the default color from the color theme. */
|
||||
public void reset(int index) {
|
||||
mCurrentColors[index] = COLOR_SCHEME.mDefaultColors[index];
|
||||
}
|
||||
/** Reset a particular indexed color with the default color from the color theme. */
|
||||
public void reset(int index) {
|
||||
mCurrentColors[index] = COLOR_SCHEME.mDefaultColors[index];
|
||||
}
|
||||
|
||||
/** Reset all indexed colors with the default color from the color theme. */
|
||||
public void reset() {
|
||||
System.arraycopy(COLOR_SCHEME.mDefaultColors, 0, mCurrentColors, 0, TextStyle.NUM_INDEXED_COLORS);
|
||||
}
|
||||
/** Reset all indexed colors with the default color from the color theme. */
|
||||
public void reset() {
|
||||
System.arraycopy(COLOR_SCHEME.mDefaultColors, 0, mCurrentColors, 0, TextStyle.NUM_INDEXED_COLORS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse color according to http://manpages.ubuntu.com/manpages/intrepid/man3/XQueryColor.3.html
|
||||
*
|
||||
* Highest bit is set if successful, so return value is 0xFF${R}${G}${B}. Return 0 if failed.
|
||||
*/
|
||||
static int parse(String c) {
|
||||
try {
|
||||
int skipInitial, skipBetween;
|
||||
if (c.charAt(0) == '#') {
|
||||
// #RGB, #RRGGBB, #RRRGGGBBB or #RRRRGGGGBBBB. Most significant bits.
|
||||
skipInitial = 1;
|
||||
skipBetween = 0;
|
||||
} else if (c.startsWith("rgb:")) {
|
||||
// rgb:<red>/<green>/<blue> where <red>, <green>, <blue> := h | hh | hhh | hhhh. Scaled.
|
||||
skipInitial = 4;
|
||||
skipBetween = 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
int charsForColors = c.length() - skipInitial - 2 * skipBetween;
|
||||
if (charsForColors % 3 != 0) return 0; // Unequal lengths.
|
||||
int componentLength = charsForColors / 3;
|
||||
double mult = 255 / (Math.pow(2, componentLength * 4) - 1);
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static int parse(String c) {
|
||||
try {
|
||||
int skipInitial, skipBetween;
|
||||
if (c.charAt(0) == '#') {
|
||||
// #RGB, #RRGGBB, #RRRGGGBBB or #RRRRGGGGBBBB. Most significant bits.
|
||||
skipInitial = 1;
|
||||
skipBetween = 0;
|
||||
} else if (c.startsWith("rgb:")) {
|
||||
// rgb:<red>/<green>/<blue> where <red>, <green>, <blue> := h | hh | hhh | hhhh. Scaled.
|
||||
skipInitial = 4;
|
||||
skipBetween = 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
int charsForColors = c.length() - skipInitial - 2 * skipBetween;
|
||||
if (charsForColors % 3 != 0) return 0; // Unequal lengths.
|
||||
int componentLength = charsForColors / 3;
|
||||
double mult = 255 / (Math.pow(2, componentLength * 4) - 1);
|
||||
|
||||
int currentPosition = skipInitial;
|
||||
String rString = c.substring(currentPosition, currentPosition + componentLength);
|
||||
currentPosition += componentLength + skipBetween;
|
||||
String gString = c.substring(currentPosition, currentPosition + componentLength);
|
||||
currentPosition += componentLength + skipBetween;
|
||||
String bString = c.substring(currentPosition, currentPosition + componentLength);
|
||||
int currentPosition = skipInitial;
|
||||
String rString = c.substring(currentPosition, currentPosition + componentLength);
|
||||
currentPosition += componentLength + skipBetween;
|
||||
String gString = c.substring(currentPosition, currentPosition + componentLength);
|
||||
currentPosition += componentLength + skipBetween;
|
||||
String bString = c.substring(currentPosition, currentPosition + componentLength);
|
||||
|
||||
int r = (int) (Integer.parseInt(rString, 16) * mult);
|
||||
int g = (int) (Integer.parseInt(gString, 16) * mult);
|
||||
int b = (int) (Integer.parseInt(bString, 16) * mult);
|
||||
return 0xFF << 24 | r << 16 | g << 8 | b;
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
int r = (int) (Integer.parseInt(rString, 16) * mult);
|
||||
int g = (int) (Integer.parseInt(gString, 16) * mult);
|
||||
int b = (int) (Integer.parseInt(bString, 16) * mult);
|
||||
return 0xFF << 24 | r << 16 | g << 8 | b;
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Try parse a color from a text parameter and into a specified index. */
|
||||
public void tryParseColor(int intoIndex, String textParameter) {
|
||||
int c = parse(textParameter);
|
||||
if (c != 0) mCurrentColors[intoIndex] = c;
|
||||
}
|
||||
/** Try parse a color from a text parameter and into a specified index. */
|
||||
public void tryParseColor(int intoIndex, String textParameter) {
|
||||
int c = parse(textParameter);
|
||||
if (c != 0) mCurrentColors[intoIndex] = c;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,23 +5,23 @@ import java.nio.charset.StandardCharsets;
|
|||
/** A client which receives callbacks from events triggered by feeding input to a {@link TerminalEmulator}. */
|
||||
public abstract class TerminalOutput {
|
||||
|
||||
/** Write a string using the UTF-8 encoding to the terminal client. */
|
||||
public final void write(String data) {
|
||||
byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
|
||||
write(bytes, 0, bytes.length);
|
||||
}
|
||||
/** Write a string using the UTF-8 encoding to the terminal client. */
|
||||
public final void write(String data) {
|
||||
byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
|
||||
write(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
/** Write bytes to the terminal client. */
|
||||
public abstract void write(byte[] data, int offset, int count);
|
||||
/** Write bytes to the terminal client. */
|
||||
public abstract void write(byte[] data, int offset, int count);
|
||||
|
||||
/** Notify the terminal client that the terminal title has changed. */
|
||||
public abstract void titleChanged(String oldTitle, String newTitle);
|
||||
/** Notify the terminal client that the terminal title has changed. */
|
||||
public abstract void titleChanged(String oldTitle, String newTitle);
|
||||
|
||||
/** Notify the terminal client that the terminal title has changed. */
|
||||
public abstract void clipboardText(String text);
|
||||
/** Notify the terminal client that the terminal title has changed. */
|
||||
public abstract void clipboardText(String text);
|
||||
|
||||
/** Notify the terminal client that a bell character (ASCII 7, bell, BEL, \a, ^G)) has been received. */
|
||||
public abstract void onBell();
|
||||
/** Notify the terminal client that a bell character (ASCII 7, bell, BEL, \a, ^G)) has been received. */
|
||||
public abstract void onBell();
|
||||
|
||||
public abstract void onColorsChanged();
|
||||
|
||||
|
|
|
@ -4,229 +4,229 @@ import java.util.Arrays;
|
|||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public final class TerminalRow {
|
||||
|
||||
private static final float SPARE_CAPACITY_FACTOR = 1.5f;
|
||||
private static final float SPARE_CAPACITY_FACTOR = 1.5f;
|
||||
|
||||
/** The number of columns in this terminal row. */
|
||||
private final int mColumns;
|
||||
/** The text filling this terminal row. */
|
||||
public char[] mText;
|
||||
/** The number of java char:s used in {@link #mText}. */
|
||||
private short mSpaceUsed;
|
||||
/** If this row has been line wrapped due to text output at the end of line. */
|
||||
boolean mLineWrap;
|
||||
/** The style bits of each cell in the row. See {@link TextStyle}. */
|
||||
final int[] mStyle;
|
||||
/** The number of columns in this terminal row. */
|
||||
private final int mColumns;
|
||||
/** The text filling this terminal row. */
|
||||
public char[] mText;
|
||||
/** The number of java char:s used in {@link #mText}. */
|
||||
private short mSpaceUsed;
|
||||
/** If this row has been line wrapped due to text output at the end of line. */
|
||||
boolean mLineWrap;
|
||||
/** The style bits of each cell in the row. See {@link TextStyle}. */
|
||||
final int[] mStyle;
|
||||
|
||||
/** Construct a blank row (containing only whitespace, ' ') with a specified style. */
|
||||
public TerminalRow(int columns, int style) {
|
||||
mColumns = columns;
|
||||
mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)];
|
||||
mStyle = new int[columns];
|
||||
clear(style);
|
||||
}
|
||||
/** Construct a blank row (containing only whitespace, ' ') with a specified style. */
|
||||
public TerminalRow(int columns, int style) {
|
||||
mColumns = columns;
|
||||
mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)];
|
||||
mStyle = new int[columns];
|
||||
clear(style);
|
||||
}
|
||||
|
||||
/** NOTE: The sourceX2 is exclusive. */
|
||||
public void copyInterval(TerminalRow line, int sourceX1, int sourceX2, int destinationX) {
|
||||
final int x1 = line.findStartOfColumn(sourceX1);
|
||||
final int x2 = line.findStartOfColumn(sourceX2);
|
||||
boolean startingFromSecondHalfOfWideChar = (sourceX1 > 0 && line.wideDisplayCharacterStartingAt(sourceX1 - 1));
|
||||
final char[] sourceChars = (this == line) ? Arrays.copyOf(line.mText, line.mText.length) : line.mText;
|
||||
int latestNonCombiningWidth = 0;
|
||||
for (int i = x1; i < x2; i++) {
|
||||
char sourceChar = sourceChars[i];
|
||||
int codePoint = Character.isHighSurrogate(sourceChar) ? Character.toCodePoint(sourceChar, sourceChars[++i]) : sourceChar;
|
||||
if (startingFromSecondHalfOfWideChar) {
|
||||
// Just treat copying second half of wide char as copying whitespace.
|
||||
codePoint = ' ';
|
||||
startingFromSecondHalfOfWideChar = false;
|
||||
}
|
||||
int w = WcWidth.width(codePoint);
|
||||
if (w > 0) {
|
||||
destinationX += latestNonCombiningWidth;
|
||||
sourceX1 += latestNonCombiningWidth;
|
||||
latestNonCombiningWidth = w;
|
||||
}
|
||||
setChar(destinationX, codePoint, line.getStyle(sourceX1));
|
||||
}
|
||||
}
|
||||
/** NOTE: The sourceX2 is exclusive. */
|
||||
public void copyInterval(TerminalRow line, int sourceX1, int sourceX2, int destinationX) {
|
||||
final int x1 = line.findStartOfColumn(sourceX1);
|
||||
final int x2 = line.findStartOfColumn(sourceX2);
|
||||
boolean startingFromSecondHalfOfWideChar = (sourceX1 > 0 && line.wideDisplayCharacterStartingAt(sourceX1 - 1));
|
||||
final char[] sourceChars = (this == line) ? Arrays.copyOf(line.mText, line.mText.length) : line.mText;
|
||||
int latestNonCombiningWidth = 0;
|
||||
for (int i = x1; i < x2; i++) {
|
||||
char sourceChar = sourceChars[i];
|
||||
int codePoint = Character.isHighSurrogate(sourceChar) ? Character.toCodePoint(sourceChar, sourceChars[++i]) : sourceChar;
|
||||
if (startingFromSecondHalfOfWideChar) {
|
||||
// Just treat copying second half of wide char as copying whitespace.
|
||||
codePoint = ' ';
|
||||
startingFromSecondHalfOfWideChar = false;
|
||||
}
|
||||
int w = WcWidth.width(codePoint);
|
||||
if (w > 0) {
|
||||
destinationX += latestNonCombiningWidth;
|
||||
sourceX1 += latestNonCombiningWidth;
|
||||
latestNonCombiningWidth = w;
|
||||
}
|
||||
setChar(destinationX, codePoint, line.getStyle(sourceX1));
|
||||
}
|
||||
}
|
||||
|
||||
public int getSpaceUsed() {
|
||||
return mSpaceUsed;
|
||||
}
|
||||
public int getSpaceUsed() {
|
||||
return mSpaceUsed;
|
||||
}
|
||||
|
||||
/** Note that the column may end of second half of wide character. */
|
||||
public int findStartOfColumn(int column) {
|
||||
if (column == mColumns) return getSpaceUsed();
|
||||
/** Note that the column may end of second half of wide character. */
|
||||
public int findStartOfColumn(int column) {
|
||||
if (column == mColumns) return getSpaceUsed();
|
||||
|
||||
int currentColumn = 0;
|
||||
int currentCharIndex = 0;
|
||||
while (true) { // 0<2 1 < 2
|
||||
int newCharIndex = currentCharIndex;
|
||||
char c = mText[newCharIndex++]; // cci=1, cci=2
|
||||
boolean isHigh = Character.isHighSurrogate(c);
|
||||
int codePoint = isHigh ? Character.toCodePoint(c, mText[newCharIndex++]) : c;
|
||||
int wcwidth = WcWidth.width(codePoint); // 1, 2
|
||||
if (wcwidth > 0) {
|
||||
currentColumn += wcwidth;
|
||||
if (currentColumn == column) {
|
||||
while (newCharIndex < mSpaceUsed) {
|
||||
// Skip combining chars.
|
||||
if (Character.isHighSurrogate(mText[newCharIndex])) {
|
||||
if (WcWidth.width(Character.toCodePoint(mText[newCharIndex], mText[newCharIndex + 1])) <= 0) {
|
||||
newCharIndex += 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if (WcWidth.width(mText[newCharIndex]) <= 0) {
|
||||
newCharIndex++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return newCharIndex;
|
||||
} else if (currentColumn > column) {
|
||||
// Wide column going past end.
|
||||
return currentCharIndex;
|
||||
}
|
||||
}
|
||||
currentCharIndex = newCharIndex;
|
||||
}
|
||||
}
|
||||
int currentColumn = 0;
|
||||
int currentCharIndex = 0;
|
||||
while (true) { // 0<2 1 < 2
|
||||
int newCharIndex = currentCharIndex;
|
||||
char c = mText[newCharIndex++]; // cci=1, cci=2
|
||||
boolean isHigh = Character.isHighSurrogate(c);
|
||||
int codePoint = isHigh ? Character.toCodePoint(c, mText[newCharIndex++]) : c;
|
||||
int wcwidth = WcWidth.width(codePoint); // 1, 2
|
||||
if (wcwidth > 0) {
|
||||
currentColumn += wcwidth;
|
||||
if (currentColumn == column) {
|
||||
while (newCharIndex < mSpaceUsed) {
|
||||
// Skip combining chars.
|
||||
if (Character.isHighSurrogate(mText[newCharIndex])) {
|
||||
if (WcWidth.width(Character.toCodePoint(mText[newCharIndex], mText[newCharIndex + 1])) <= 0) {
|
||||
newCharIndex += 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if (WcWidth.width(mText[newCharIndex]) <= 0) {
|
||||
newCharIndex++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return newCharIndex;
|
||||
} else if (currentColumn > column) {
|
||||
// Wide column going past end.
|
||||
return currentCharIndex;
|
||||
}
|
||||
}
|
||||
currentCharIndex = newCharIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean wideDisplayCharacterStartingAt(int column) {
|
||||
for (int currentCharIndex = 0, currentColumn = 0; currentCharIndex < mSpaceUsed;) {
|
||||
char c = mText[currentCharIndex++];
|
||||
int codePoint = Character.isHighSurrogate(c) ? Character.toCodePoint(c, mText[currentCharIndex++]) : c;
|
||||
int wcwidth = WcWidth.width(codePoint);
|
||||
if (wcwidth > 0) {
|
||||
if (currentColumn == column && wcwidth == 2) return true;
|
||||
currentColumn += wcwidth;
|
||||
if (currentColumn > column) return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private boolean wideDisplayCharacterStartingAt(int column) {
|
||||
for (int currentCharIndex = 0, currentColumn = 0; currentCharIndex < mSpaceUsed; ) {
|
||||
char c = mText[currentCharIndex++];
|
||||
int codePoint = Character.isHighSurrogate(c) ? Character.toCodePoint(c, mText[currentCharIndex++]) : c;
|
||||
int wcwidth = WcWidth.width(codePoint);
|
||||
if (wcwidth > 0) {
|
||||
if (currentColumn == column && wcwidth == 2) return true;
|
||||
currentColumn += wcwidth;
|
||||
if (currentColumn > column) return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void clear(int style) {
|
||||
Arrays.fill(mText, ' ');
|
||||
Arrays.fill(mStyle, style);
|
||||
mSpaceUsed = (short) mColumns;
|
||||
}
|
||||
public void clear(int style) {
|
||||
Arrays.fill(mText, ' ');
|
||||
Arrays.fill(mStyle, style);
|
||||
mSpaceUsed = (short) mColumns;
|
||||
}
|
||||
|
||||
// https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26
|
||||
public void setChar(int columnToSet, int codePoint, int style) {
|
||||
mStyle[columnToSet] = style;
|
||||
// https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26
|
||||
public void setChar(int columnToSet, int codePoint, int style) {
|
||||
mStyle[columnToSet] = style;
|
||||
|
||||
final int newCodePointDisplayWidth = WcWidth.width(codePoint);
|
||||
final boolean newIsCombining = newCodePointDisplayWidth <= 0;
|
||||
final int newCodePointDisplayWidth = WcWidth.width(codePoint);
|
||||
final boolean newIsCombining = newCodePointDisplayWidth <= 0;
|
||||
|
||||
boolean wasExtraColForWideChar = (columnToSet > 0) && wideDisplayCharacterStartingAt(columnToSet - 1);
|
||||
boolean wasExtraColForWideChar = (columnToSet > 0) && wideDisplayCharacterStartingAt(columnToSet - 1);
|
||||
|
||||
if (newIsCombining) {
|
||||
// When standing at second half of wide character and inserting combining:
|
||||
if (wasExtraColForWideChar) columnToSet--;
|
||||
} else {
|
||||
// Check if we are overwriting the second half of a wide character starting at the previous column:
|
||||
if (wasExtraColForWideChar) setChar(columnToSet - 1, ' ', style);
|
||||
// Check if we are overwriting the first half of a wide character starting at the next column:
|
||||
boolean overwritingWideCharInNextColumn = newCodePointDisplayWidth == 2 && wideDisplayCharacterStartingAt(columnToSet + 1);
|
||||
if (overwritingWideCharInNextColumn) setChar(columnToSet + 1, ' ', style);
|
||||
}
|
||||
if (newIsCombining) {
|
||||
// When standing at second half of wide character and inserting combining:
|
||||
if (wasExtraColForWideChar) columnToSet--;
|
||||
} else {
|
||||
// Check if we are overwriting the second half of a wide character starting at the previous column:
|
||||
if (wasExtraColForWideChar) setChar(columnToSet - 1, ' ', style);
|
||||
// Check if we are overwriting the first half of a wide character starting at the next column:
|
||||
boolean overwritingWideCharInNextColumn = newCodePointDisplayWidth == 2 && wideDisplayCharacterStartingAt(columnToSet + 1);
|
||||
if (overwritingWideCharInNextColumn) setChar(columnToSet + 1, ' ', style);
|
||||
}
|
||||
|
||||
char[] text = mText;
|
||||
final int oldStartOfColumnIndex = findStartOfColumn(columnToSet);
|
||||
final int oldCodePointDisplayWidth = WcWidth.width(text, oldStartOfColumnIndex);
|
||||
char[] text = mText;
|
||||
final int oldStartOfColumnIndex = findStartOfColumn(columnToSet);
|
||||
final int oldCodePointDisplayWidth = WcWidth.width(text, oldStartOfColumnIndex);
|
||||
|
||||
// Get the number of elements in the mText array this column uses now
|
||||
int oldCharactersUsedForColumn;
|
||||
if (columnToSet + oldCodePointDisplayWidth < mColumns) {
|
||||
oldCharactersUsedForColumn = findStartOfColumn(columnToSet + oldCodePointDisplayWidth) - oldStartOfColumnIndex;
|
||||
} else {
|
||||
// Last character.
|
||||
oldCharactersUsedForColumn = mSpaceUsed - oldStartOfColumnIndex;
|
||||
}
|
||||
// Get the number of elements in the mText array this column uses now
|
||||
int oldCharactersUsedForColumn;
|
||||
if (columnToSet + oldCodePointDisplayWidth < mColumns) {
|
||||
oldCharactersUsedForColumn = findStartOfColumn(columnToSet + oldCodePointDisplayWidth) - oldStartOfColumnIndex;
|
||||
} else {
|
||||
// Last character.
|
||||
oldCharactersUsedForColumn = mSpaceUsed - oldStartOfColumnIndex;
|
||||
}
|
||||
|
||||
// Find how many chars this column will need
|
||||
int newCharactersUsedForColumn = Character.charCount(codePoint);
|
||||
if (newIsCombining) {
|
||||
// Combining characters are added to the contents of the column instead of overwriting them, so that they
|
||||
// modify the existing contents.
|
||||
// FIXME: Put a limit of combining characters.
|
||||
// FIXME: Unassigned characters also get width=0.
|
||||
newCharactersUsedForColumn += oldCharactersUsedForColumn;
|
||||
}
|
||||
// Find how many chars this column will need
|
||||
int newCharactersUsedForColumn = Character.charCount(codePoint);
|
||||
if (newIsCombining) {
|
||||
// Combining characters are added to the contents of the column instead of overwriting them, so that they
|
||||
// modify the existing contents.
|
||||
// FIXME: Put a limit of combining characters.
|
||||
// FIXME: Unassigned characters also get width=0.
|
||||
newCharactersUsedForColumn += oldCharactersUsedForColumn;
|
||||
}
|
||||
|
||||
int oldNextColumnIndex = oldStartOfColumnIndex + oldCharactersUsedForColumn;
|
||||
int newNextColumnIndex = oldStartOfColumnIndex + newCharactersUsedForColumn;
|
||||
int oldNextColumnIndex = oldStartOfColumnIndex + oldCharactersUsedForColumn;
|
||||
int newNextColumnIndex = oldStartOfColumnIndex + newCharactersUsedForColumn;
|
||||
|
||||
final int javaCharDifference = newCharactersUsedForColumn - oldCharactersUsedForColumn;
|
||||
if (javaCharDifference > 0) {
|
||||
// Shift the rest of the line right.
|
||||
int oldCharactersAfterColumn = mSpaceUsed - oldNextColumnIndex;
|
||||
if (mSpaceUsed + javaCharDifference > text.length) {
|
||||
// We need to grow the array
|
||||
char[] newText = new char[text.length + mColumns];
|
||||
System.arraycopy(text, 0, newText, 0, oldStartOfColumnIndex + oldCharactersUsedForColumn);
|
||||
System.arraycopy(text, oldNextColumnIndex, newText, newNextColumnIndex, oldCharactersAfterColumn);
|
||||
mText = text = newText;
|
||||
} else {
|
||||
System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, oldCharactersAfterColumn);
|
||||
}
|
||||
} else if (javaCharDifference < 0) {
|
||||
// Shift the rest of the line left.
|
||||
System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - oldNextColumnIndex);
|
||||
}
|
||||
mSpaceUsed += javaCharDifference;
|
||||
final int javaCharDifference = newCharactersUsedForColumn - oldCharactersUsedForColumn;
|
||||
if (javaCharDifference > 0) {
|
||||
// Shift the rest of the line right.
|
||||
int oldCharactersAfterColumn = mSpaceUsed - oldNextColumnIndex;
|
||||
if (mSpaceUsed + javaCharDifference > text.length) {
|
||||
// We need to grow the array
|
||||
char[] newText = new char[text.length + mColumns];
|
||||
System.arraycopy(text, 0, newText, 0, oldStartOfColumnIndex + oldCharactersUsedForColumn);
|
||||
System.arraycopy(text, oldNextColumnIndex, newText, newNextColumnIndex, oldCharactersAfterColumn);
|
||||
mText = text = newText;
|
||||
} else {
|
||||
System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, oldCharactersAfterColumn);
|
||||
}
|
||||
} else if (javaCharDifference < 0) {
|
||||
// Shift the rest of the line left.
|
||||
System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - oldNextColumnIndex);
|
||||
}
|
||||
mSpaceUsed += javaCharDifference;
|
||||
|
||||
// Store char. A combining character is stored at the end of the existing contents so that it modifies them:
|
||||
//noinspection ResultOfMethodCallIgnored - since we already now how many java chars is used.
|
||||
Character.toChars(codePoint, text, oldStartOfColumnIndex + (newIsCombining ? oldCharactersUsedForColumn : 0));
|
||||
// Store char. A combining character is stored at the end of the existing contents so that it modifies them:
|
||||
//noinspection ResultOfMethodCallIgnored - since we already now how many java chars is used.
|
||||
Character.toChars(codePoint, text, oldStartOfColumnIndex + (newIsCombining ? oldCharactersUsedForColumn : 0));
|
||||
|
||||
if (oldCodePointDisplayWidth == 2 && newCodePointDisplayWidth == 1) {
|
||||
// Replace second half of wide char with a space. Which mean that we actually add a ' ' java character.
|
||||
if (mSpaceUsed + 1 > text.length) {
|
||||
char[] newText = new char[text.length + mColumns];
|
||||
System.arraycopy(text, 0, newText, 0, newNextColumnIndex);
|
||||
System.arraycopy(text, newNextColumnIndex, newText, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex);
|
||||
mText = text = newText;
|
||||
} else {
|
||||
System.arraycopy(text, newNextColumnIndex, text, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex);
|
||||
}
|
||||
text[newNextColumnIndex] = ' ';
|
||||
if (oldCodePointDisplayWidth == 2 && newCodePointDisplayWidth == 1) {
|
||||
// Replace second half of wide char with a space. Which mean that we actually add a ' ' java character.
|
||||
if (mSpaceUsed + 1 > text.length) {
|
||||
char[] newText = new char[text.length + mColumns];
|
||||
System.arraycopy(text, 0, newText, 0, newNextColumnIndex);
|
||||
System.arraycopy(text, newNextColumnIndex, newText, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex);
|
||||
mText = text = newText;
|
||||
} else {
|
||||
System.arraycopy(text, newNextColumnIndex, text, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex);
|
||||
}
|
||||
text[newNextColumnIndex] = ' ';
|
||||
|
||||
++mSpaceUsed;
|
||||
} else if (oldCodePointDisplayWidth == 1 && newCodePointDisplayWidth == 2) {
|
||||
if (columnToSet == mColumns - 1) {
|
||||
throw new IllegalArgumentException("Cannot put wide character in last column");
|
||||
} else if (columnToSet == mColumns - 2) {
|
||||
// Truncate the line to the second part of this wide char:
|
||||
mSpaceUsed = (short) newNextColumnIndex;
|
||||
} else {
|
||||
// Overwrite the contents of the next column, which mean we actually remove java characters. Due to the
|
||||
// check at the beginning of this method we know that we are not overwriting a wide char.
|
||||
int newNextNextColumnIndex = newNextColumnIndex + (Character.isHighSurrogate(mText[newNextColumnIndex]) ? 2 : 1);
|
||||
int nextLen = newNextNextColumnIndex - newNextColumnIndex;
|
||||
++mSpaceUsed;
|
||||
} else if (oldCodePointDisplayWidth == 1 && newCodePointDisplayWidth == 2) {
|
||||
if (columnToSet == mColumns - 1) {
|
||||
throw new IllegalArgumentException("Cannot put wide character in last column");
|
||||
} else if (columnToSet == mColumns - 2) {
|
||||
// Truncate the line to the second part of this wide char:
|
||||
mSpaceUsed = (short) newNextColumnIndex;
|
||||
} else {
|
||||
// Overwrite the contents of the next column, which mean we actually remove java characters. Due to the
|
||||
// check at the beginning of this method we know that we are not overwriting a wide char.
|
||||
int newNextNextColumnIndex = newNextColumnIndex + (Character.isHighSurrogate(mText[newNextColumnIndex]) ? 2 : 1);
|
||||
int nextLen = newNextNextColumnIndex - newNextColumnIndex;
|
||||
|
||||
// Shift the array leftwards.
|
||||
System.arraycopy(text, newNextNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - newNextNextColumnIndex);
|
||||
mSpaceUsed -= nextLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Shift the array leftwards.
|
||||
System.arraycopy(text, newNextNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - newNextNextColumnIndex);
|
||||
mSpaceUsed -= nextLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isBlank() {
|
||||
for (int charIndex = 0, charLen = getSpaceUsed(); charIndex < charLen; charIndex++)
|
||||
if (mText[charIndex] != ' ') return false;
|
||||
return true;
|
||||
}
|
||||
boolean isBlank() {
|
||||
for (int charIndex = 0, charLen = getSpaceUsed(); charIndex < charLen; charIndex++)
|
||||
if (mText[charIndex] != ' ') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public final int getStyle(int column) {
|
||||
return mStyle[column];
|
||||
}
|
||||
public final int getStyle(int column) {
|
||||
return mStyle[column];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,202 +19,200 @@ import java.util.UUID;
|
|||
|
||||
/**
|
||||
* 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
|
||||
* {@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.
|
||||
* <p>
|
||||
* <p/>
|
||||
* 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!
|
||||
*/
|
||||
public final class TerminalSession extends TerminalOutput {
|
||||
|
||||
/** Callback to be invoked when a {@link TerminalSession} changes. */
|
||||
public interface SessionChangedCallback {
|
||||
void onTextChanged(TerminalSession changedSession);
|
||||
/** Callback to be invoked when a {@link TerminalSession} changes. */
|
||||
public interface SessionChangedCallback {
|
||||
void onTextChanged(TerminalSession changedSession);
|
||||
|
||||
void onTitleChanged(TerminalSession changedSession);
|
||||
void onTitleChanged(TerminalSession changedSession);
|
||||
|
||||
void onSessionFinished(TerminalSession finishedSession);
|
||||
void onSessionFinished(TerminalSession finishedSession);
|
||||
|
||||
void onClipboardText(TerminalSession session, String text);
|
||||
void onClipboardText(TerminalSession session, String text);
|
||||
|
||||
void onBell(TerminalSession session);
|
||||
void onBell(TerminalSession session);
|
||||
|
||||
void onColorsChanged(TerminalSession session);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static FileDescriptor wrapFileDescriptor(int fileDescriptor) {
|
||||
FileDescriptor result = new FileDescriptor();
|
||||
try {
|
||||
Field descriptorField;
|
||||
try {
|
||||
descriptorField = FileDescriptor.class.getDeclaredField("descriptor");
|
||||
} catch (NoSuchFieldException e) {
|
||||
// For desktop java:
|
||||
descriptorField = FileDescriptor.class.getDeclaredField("fd");
|
||||
}
|
||||
descriptorField.setAccessible(true);
|
||||
descriptorField.set(result, fileDescriptor);
|
||||
} catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) {
|
||||
Log.wtf(EmulatorDebug.LOG_TAG, "Error accessing FileDescriptor#descriptor private field", e);
|
||||
System.exit(1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private static FileDescriptor wrapFileDescriptor(int fileDescriptor) {
|
||||
FileDescriptor result = new FileDescriptor();
|
||||
try {
|
||||
Field descriptorField;
|
||||
try {
|
||||
descriptorField = FileDescriptor.class.getDeclaredField("descriptor");
|
||||
} catch (NoSuchFieldException e) {
|
||||
// For desktop java:
|
||||
descriptorField = FileDescriptor.class.getDeclaredField("fd");
|
||||
}
|
||||
descriptorField.setAccessible(true);
|
||||
descriptorField.set(result, fileDescriptor);
|
||||
} catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) {
|
||||
Log.wtf(EmulatorDebug.LOG_TAG, "Error accessing FileDescriptor#descriptor private field", e);
|
||||
System.exit(1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final int MSG_NEW_INPUT = 1;
|
||||
private static final int MSG_PROCESS_EXITED = 4;
|
||||
private static final int MSG_NEW_INPUT = 1;
|
||||
private static final int MSG_PROCESS_EXITED = 4;
|
||||
|
||||
public final String mHandle = UUID.randomUUID().toString();
|
||||
public final String mHandle = UUID.randomUUID().toString();
|
||||
|
||||
TerminalEmulator mEmulator;
|
||||
TerminalEmulator mEmulator;
|
||||
|
||||
/**
|
||||
* A queue written to from a separate thread when the process outputs, and read by main thread to process by
|
||||
* terminal emulator.
|
||||
*/
|
||||
final ByteQueue mProcessToTerminalIOQueue = new ByteQueue(4096);
|
||||
/**
|
||||
* A queue written to from the main thread due to user interaction, and read by another thread which forwards by
|
||||
* writing to the {@link #mTerminalFileDescriptor}.
|
||||
*/
|
||||
final ByteQueue mTerminalToProcessIOQueue = new ByteQueue(4096);
|
||||
/** Buffer to write translate code points into utf8 before writing to mTerminalToProcessIOQueue */
|
||||
private final byte[] mUtf8InputBuffer = new byte[5];
|
||||
/**
|
||||
* A queue written to from a separate thread when the process outputs, and read by main thread to process by
|
||||
* terminal emulator.
|
||||
*/
|
||||
final ByteQueue mProcessToTerminalIOQueue = new ByteQueue(4096);
|
||||
/**
|
||||
* A queue written to from the main thread due to user interaction, and read by another thread which forwards by
|
||||
* writing to the {@link #mTerminalFileDescriptor}.
|
||||
*/
|
||||
final ByteQueue mTerminalToProcessIOQueue = new ByteQueue(4096);
|
||||
/** Buffer to write translate code points into utf8 before writing to mTerminalToProcessIOQueue */
|
||||
private final byte[] mUtf8InputBuffer = new byte[5];
|
||||
|
||||
/** Callback which gets notified when a session finishes or changes title. */
|
||||
final SessionChangedCallback mChangeCallback;
|
||||
/** Callback which gets notified when a session finishes or changes title. */
|
||||
final SessionChangedCallback mChangeCallback;
|
||||
|
||||
/** The pid of the shell process. 0 if not started and -1 if finished running. */
|
||||
int mShellPid;
|
||||
/** The pid of the shell process. 0 if not started and -1 if finished running. */
|
||||
int mShellPid;
|
||||
|
||||
/** The exit status of the shell process. Only valid if ${@link #mShellPid} is -1. */
|
||||
int mShellExitStatus;
|
||||
/** 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
|
||||
* {@link JNI#createSubprocess(String, String, String[], String[], int[], int, int)}.
|
||||
*/
|
||||
private int mTerminalFileDescriptor;
|
||||
/**
|
||||
* The file descriptor referencing the master half of a pseudo-terminal pair, resulting from calling
|
||||
* {@link JNI#createSubprocess(String, String, String[], String[], int[], int, int)}.
|
||||
*/
|
||||
private int mTerminalFileDescriptor;
|
||||
|
||||
/** Set by the application for user identification of session, not by terminal. */
|
||||
public String mSessionName;
|
||||
/** Set by the application for user identification of session, not by terminal. */
|
||||
public String mSessionName;
|
||||
|
||||
@SuppressLint("HandlerLeak")
|
||||
final Handler mMainThreadHandler = new Handler() {
|
||||
final byte[] mReceiveBuffer = new byte[4 * 1024];
|
||||
@SuppressLint("HandlerLeak")
|
||||
final Handler mMainThreadHandler = new Handler() {
|
||||
final byte[] mReceiveBuffer = new byte[4 * 1024];
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
if (msg.what == MSG_NEW_INPUT && isRunning()) {
|
||||
int bytesRead = mProcessToTerminalIOQueue.read(mReceiveBuffer, false);
|
||||
if (bytesRead > 0) {
|
||||
mEmulator.append(mReceiveBuffer, bytesRead);
|
||||
notifyScreenUpdate();
|
||||
}
|
||||
} else if (msg.what == MSG_PROCESS_EXITED) {
|
||||
int exitCode = (Integer) msg.obj;
|
||||
cleanupResources(exitCode);
|
||||
mChangeCallback.onSessionFinished(TerminalSession.this);
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
if (msg.what == MSG_NEW_INPUT && isRunning()) {
|
||||
int bytesRead = mProcessToTerminalIOQueue.read(mReceiveBuffer, false);
|
||||
if (bytesRead > 0) {
|
||||
mEmulator.append(mReceiveBuffer, bytesRead);
|
||||
notifyScreenUpdate();
|
||||
}
|
||||
} else if (msg.what == MSG_PROCESS_EXITED) {
|
||||
int exitCode = (Integer) msg.obj;
|
||||
cleanupResources(exitCode);
|
||||
mChangeCallback.onSessionFinished(TerminalSession.this);
|
||||
|
||||
String exitDescription = "\r\n[Process completed";
|
||||
if (exitCode > 0) {
|
||||
// Non-zero process exit.
|
||||
exitDescription += " with code " + exitCode;
|
||||
} else if (exitCode < 0) {
|
||||
// Negated signal.
|
||||
exitDescription += " with signal " + (-exitCode);
|
||||
}
|
||||
exitDescription += " - press Enter to close]";
|
||||
String exitDescription = "\r\n[Process completed";
|
||||
if (exitCode > 0) {
|
||||
// Non-zero process exit.
|
||||
exitDescription += " with code " + exitCode;
|
||||
} else if (exitCode < 0) {
|
||||
// Negated signal.
|
||||
exitDescription += " with signal " + (-exitCode);
|
||||
}
|
||||
exitDescription += " - press Enter to close]";
|
||||
|
||||
byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
|
||||
mEmulator.append(bytesToWrite, bytesToWrite.length);
|
||||
notifyScreenUpdate();
|
||||
}
|
||||
}
|
||||
};
|
||||
byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
|
||||
mEmulator.append(bytesToWrite, bytesToWrite.length);
|
||||
notifyScreenUpdate();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final String mShellPath;
|
||||
private final String mCwd;
|
||||
private final String[] mArgs;
|
||||
private final String[] mEnv;
|
||||
private final String mShellPath;
|
||||
private final String mCwd;
|
||||
private final String[] mArgs;
|
||||
private final String[] mEnv;
|
||||
|
||||
public TerminalSession(String shellPath, String cwd, String[] args, String[] env, SessionChangedCallback changeCallback) {
|
||||
mChangeCallback = changeCallback;
|
||||
public TerminalSession(String shellPath, String cwd, String[] args, String[] env, SessionChangedCallback changeCallback) {
|
||||
mChangeCallback = changeCallback;
|
||||
|
||||
this.mShellPath = shellPath;
|
||||
this.mCwd = cwd;
|
||||
this.mArgs = args;
|
||||
this.mEnv = env;
|
||||
}
|
||||
this.mShellPath = shellPath;
|
||||
this.mCwd = cwd;
|
||||
this.mArgs = args;
|
||||
this.mEnv = env;
|
||||
}
|
||||
|
||||
/** Inform the attached pty of the new size and reflow or initialize the emulator. */
|
||||
public void updateSize(int columns, int rows) {
|
||||
if (mEmulator == null) {
|
||||
initializeEmulator(columns, rows);
|
||||
} else {
|
||||
JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns);
|
||||
mEmulator.resize(columns, rows);
|
||||
}
|
||||
}
|
||||
/** Inform the attached pty of the new size and reflow or initialize the emulator. */
|
||||
public void updateSize(int columns, int rows) {
|
||||
if (mEmulator == null) {
|
||||
initializeEmulator(columns, rows);
|
||||
} else {
|
||||
JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns);
|
||||
mEmulator.resize(columns, rows);
|
||||
}
|
||||
}
|
||||
|
||||
/** The terminal title as set through escape sequences or null if none set. */
|
||||
public String getTitle() {
|
||||
return (mEmulator == null) ? null : mEmulator.getTitle();
|
||||
}
|
||||
/** The terminal title as set through escape sequences or null if none set. */
|
||||
public String getTitle() {
|
||||
return (mEmulator == null) ? null : mEmulator.getTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the terminal emulator's window size and start terminal emulation.
|
||||
*
|
||||
* @param columns
|
||||
* The number of columns in the terminal window.
|
||||
* @param rows
|
||||
* The number of rows in the terminal window.
|
||||
*/
|
||||
public void initializeEmulator(int columns, int rows) {
|
||||
mEmulator = new TerminalEmulator(this, columns, rows, /* transcript= */5000);
|
||||
/**
|
||||
* Set the terminal emulator's window size and start terminal emulation.
|
||||
*
|
||||
* @param columns The number of columns in the terminal window.
|
||||
* @param rows The number of rows in the terminal window.
|
||||
*/
|
||||
public void initializeEmulator(int columns, int rows) {
|
||||
mEmulator = new TerminalEmulator(this, columns, rows, /* transcript= */5000);
|
||||
|
||||
int[] processId = new int[1];
|
||||
mTerminalFileDescriptor = JNI.createSubprocess(mShellPath, mCwd, mArgs, mEnv, processId, rows, columns);
|
||||
mShellPid = processId[0];
|
||||
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 + "]") {
|
||||
@Override
|
||||
public void run() {
|
||||
try (InputStream termIn = new FileInputStream(terminalFileDescriptorWrapped)) {
|
||||
final byte[] buffer = new byte[4096];
|
||||
while (true) {
|
||||
int read = termIn.read(buffer);
|
||||
if (read == -1) return;
|
||||
if (!mProcessToTerminalIOQueue.write(buffer, 0, read)) return;
|
||||
mMainThreadHandler.sendEmptyMessage(MSG_NEW_INPUT);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore, just shutting down.
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
new Thread("TermSessionInputReader[pid=" + mShellPid + "]") {
|
||||
@Override
|
||||
public void run() {
|
||||
try (InputStream termIn = new FileInputStream(terminalFileDescriptorWrapped)) {
|
||||
final byte[] buffer = new byte[4096];
|
||||
while (true) {
|
||||
int read = termIn.read(buffer);
|
||||
if (read == -1) return;
|
||||
if (!mProcessToTerminalIOQueue.write(buffer, 0, read)) return;
|
||||
mMainThreadHandler.sendEmptyMessage(MSG_NEW_INPUT);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore, just shutting down.
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
|
||||
new Thread("TermSessionOutputWriter[pid=" + mShellPid + "]") {
|
||||
@Override
|
||||
public void run() {
|
||||
final byte[] buffer = new byte[4096];
|
||||
try (FileOutputStream termOut = new FileOutputStream(terminalFileDescriptorWrapped)) {
|
||||
while (true) {
|
||||
int bytesToWrite = mTerminalToProcessIOQueue.read(buffer, true);
|
||||
if (bytesToWrite == -1) return;
|
||||
termOut.write(buffer, 0, bytesToWrite);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
new Thread("TermSessionOutputWriter[pid=" + mShellPid + "]") {
|
||||
@Override
|
||||
public void run() {
|
||||
final byte[] buffer = new byte[4096];
|
||||
try (FileOutputStream termOut = new FileOutputStream(terminalFileDescriptorWrapped)) {
|
||||
while (true) {
|
||||
int bytesToWrite = mTerminalToProcessIOQueue.read(buffer, true);
|
||||
if (bytesToWrite == -1) return;
|
||||
termOut.write(buffer, 0, bytesToWrite);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
|
||||
new Thread("TermSessionWaiter[pid=" + mShellPid + "]") {
|
||||
@Override
|
||||
|
@ -226,63 +224,63 @@ public final class TerminalSession extends TerminalOutput {
|
|||
|
||||
}
|
||||
|
||||
/** Write data to the shell process. */
|
||||
@Override
|
||||
public void write(byte[] data, int offset, int count) {
|
||||
if (mShellPid > 0) mTerminalToProcessIOQueue.write(data, offset, count);
|
||||
}
|
||||
/** Write data to the shell process. */
|
||||
@Override
|
||||
public void write(byte[] data, int offset, int count) {
|
||||
if (mShellPid > 0) mTerminalToProcessIOQueue.write(data, offset, count);
|
||||
}
|
||||
|
||||
/** Write the Unicode code point to the terminal encoded in UTF-8. */
|
||||
public void writeCodePoint(boolean prependEscape, int codePoint) {
|
||||
if (codePoint > 1114111 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
|
||||
// 1114111 (= 2**16 + 1024**2 - 1) is the highest code point, [0xD800,0xDFFF] is the surrogate range.
|
||||
throw new IllegalArgumentException("Invalid code point: " + codePoint);
|
||||
}
|
||||
/** Write the Unicode code point to the terminal encoded in UTF-8. */
|
||||
public void writeCodePoint(boolean prependEscape, int codePoint) {
|
||||
if (codePoint > 1114111 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
|
||||
// 1114111 (= 2**16 + 1024**2 - 1) is the highest code point, [0xD800,0xDFFF] is the surrogate range.
|
||||
throw new IllegalArgumentException("Invalid code point: " + codePoint);
|
||||
}
|
||||
|
||||
int bufferPosition = 0;
|
||||
if (prependEscape) mUtf8InputBuffer[bufferPosition++] = 27;
|
||||
int bufferPosition = 0;
|
||||
if (prependEscape) mUtf8InputBuffer[bufferPosition++] = 27;
|
||||
|
||||
if (codePoint <= /* 7 bits */0b1111111) {
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) codePoint;
|
||||
} else if (codePoint <= /* 11 bits */0b11111111111) {
|
||||
/* 110xxxxx leading byte with leading 5 bits */
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b11000000 | (codePoint >> 6));
|
||||
if (codePoint <= /* 7 bits */0b1111111) {
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) codePoint;
|
||||
} else if (codePoint <= /* 11 bits */0b11111111111) {
|
||||
/* 110xxxxx leading byte with leading 5 bits */
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b11000000 | (codePoint >> 6));
|
||||
/* 10xxxxxx continuation byte with following 6 bits */
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111));
|
||||
} else if (codePoint <= /* 16 bits */0b1111111111111111) {
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111));
|
||||
} else if (codePoint <= /* 16 bits */0b1111111111111111) {
|
||||
/* 1110xxxx leading byte with leading 4 bits */
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b11100000 | (codePoint >> 12));
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b11100000 | (codePoint >> 12));
|
||||
/* 10xxxxxx continuation byte with following 6 bits */
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111));
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111));
|
||||
/* 10xxxxxx continuation byte with following 6 bits */
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111));
|
||||
} else { /* We have checked codePoint <= 1114111 above, so we have max 21 bits = 0b111111111111111111111 */
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111));
|
||||
} else { /* We have checked codePoint <= 1114111 above, so we have max 21 bits = 0b111111111111111111111 */
|
||||
/* 11110xxx leading byte with leading 3 bits */
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b11110000 | (codePoint >> 18));
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b11110000 | (codePoint >> 18));
|
||||
/* 10xxxxxx continuation byte with following 6 bits */
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 12) & 0b111111));
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 12) & 0b111111));
|
||||
/* 10xxxxxx continuation byte with following 6 bits */
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111));
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111));
|
||||
/* 10xxxxxx continuation byte with following 6 bits */
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111));
|
||||
}
|
||||
write(mUtf8InputBuffer, 0, bufferPosition);
|
||||
}
|
||||
mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111));
|
||||
}
|
||||
write(mUtf8InputBuffer, 0, bufferPosition);
|
||||
}
|
||||
|
||||
public TerminalEmulator getEmulator() {
|
||||
return mEmulator;
|
||||
}
|
||||
public TerminalEmulator getEmulator() {
|
||||
return mEmulator;
|
||||
}
|
||||
|
||||
/** Notify the {@link #mChangeCallback} that the screen has changed. */
|
||||
protected void notifyScreenUpdate() {
|
||||
mChangeCallback.onTextChanged(this);
|
||||
}
|
||||
/** Notify the {@link #mChangeCallback} that the screen has changed. */
|
||||
protected void notifyScreenUpdate() {
|
||||
mChangeCallback.onTextChanged(this);
|
||||
}
|
||||
|
||||
/** Reset state for terminal emulator state. */
|
||||
public void reset() {
|
||||
mEmulator.reset();
|
||||
notifyScreenUpdate();
|
||||
}
|
||||
/** Reset state for terminal emulator state. */
|
||||
public void reset() {
|
||||
mEmulator.reset();
|
||||
notifyScreenUpdate();
|
||||
}
|
||||
|
||||
/** Finish this terminal session by sending SIGKILL to the shell. */
|
||||
public void finishIfRunning() {
|
||||
|
@ -295,48 +293,50 @@ public final class TerminalSession extends TerminalOutput {
|
|||
}
|
||||
}
|
||||
|
||||
/** Cleanup resources when the process exits. */
|
||||
void cleanupResources(int exitStatus) {
|
||||
synchronized (this) {
|
||||
mShellPid = -1;
|
||||
mShellExitStatus = exitStatus;
|
||||
}
|
||||
/** Cleanup resources when the process exits. */
|
||||
void cleanupResources(int exitStatus) {
|
||||
synchronized (this) {
|
||||
mShellPid = -1;
|
||||
mShellExitStatus = exitStatus;
|
||||
}
|
||||
|
||||
// Stop the reader and writer threads, and close the I/O streams
|
||||
mTerminalToProcessIOQueue.close();
|
||||
mProcessToTerminalIOQueue.close();
|
||||
JNI.close(mTerminalFileDescriptor);
|
||||
}
|
||||
// Stop the reader and writer threads, and close the I/O streams
|
||||
mTerminalToProcessIOQueue.close();
|
||||
mProcessToTerminalIOQueue.close();
|
||||
JNI.close(mTerminalFileDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void titleChanged(String oldTitle, String newTitle) {
|
||||
mChangeCallback.onTitleChanged(this);
|
||||
}
|
||||
@Override
|
||||
public void titleChanged(String oldTitle, String newTitle) {
|
||||
mChangeCallback.onTitleChanged(this);
|
||||
}
|
||||
|
||||
public synchronized boolean isRunning() {
|
||||
return mShellPid != -1;
|
||||
}
|
||||
public synchronized boolean isRunning() {
|
||||
return mShellPid != -1;
|
||||
}
|
||||
|
||||
/** Only valid if not {@link #isRunning()}. */
|
||||
public synchronized int getExitStatus() {
|
||||
return mShellExitStatus;
|
||||
}
|
||||
/** Only valid if not {@link #isRunning()}. */
|
||||
public synchronized int getExitStatus() {
|
||||
return mShellExitStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clipboardText(String text) {
|
||||
mChangeCallback.onClipboardText(this, text);
|
||||
}
|
||||
@Override
|
||||
public void clipboardText(String text) {
|
||||
mChangeCallback.onClipboardText(this, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBell() {
|
||||
mChangeCallback.onBell(this);
|
||||
}
|
||||
@Override
|
||||
public void onBell() {
|
||||
mChangeCallback.onBell(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorsChanged() {
|
||||
mChangeCallback.onColorsChanged(this);
|
||||
}
|
||||
|
||||
public int getPid() { return mShellPid; }
|
||||
public int getPid() {
|
||||
return mShellPid;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,53 +3,53 @@ package com.termux.terminal;
|
|||
/**
|
||||
* Encodes effects, foreground and background colors into a 32 bit integer, which are stored for each cell in a terminal
|
||||
* 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 different CHARACTER_ATTRIBUTE_* bits).
|
||||
*/
|
||||
public final class TextStyle {
|
||||
|
||||
public final static int CHARACTER_ATTRIBUTE_BOLD = 1;
|
||||
public final static int CHARACTER_ATTRIBUTE_ITALIC = 1 << 1;
|
||||
public final static int CHARACTER_ATTRIBUTE_UNDERLINE = 1 << 2;
|
||||
public final static int CHARACTER_ATTRIBUTE_BLINK = 1 << 3;
|
||||
public final static int CHARACTER_ATTRIBUTE_INVERSE = 1 << 4;
|
||||
public final static int CHARACTER_ATTRIBUTE_INVISIBLE = 1 << 5;
|
||||
public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6;
|
||||
/**
|
||||
* The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7;
|
||||
/** Dim colors. Also known as faint or half intensity. */
|
||||
public final static int CHARACTER_ATTRIBUTE_DIM = 1 << 8;
|
||||
public final static int CHARACTER_ATTRIBUTE_BOLD = 1;
|
||||
public final static int CHARACTER_ATTRIBUTE_ITALIC = 1 << 1;
|
||||
public final static int CHARACTER_ATTRIBUTE_UNDERLINE = 1 << 2;
|
||||
public final static int CHARACTER_ATTRIBUTE_BLINK = 1 << 3;
|
||||
public final static int CHARACTER_ATTRIBUTE_INVERSE = 1 << 4;
|
||||
public final static int CHARACTER_ATTRIBUTE_INVISIBLE = 1 << 5;
|
||||
public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6;
|
||||
/**
|
||||
* The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable.
|
||||
* <p/>
|
||||
* This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that
|
||||
* come after it as erasable from the screen.
|
||||
*/
|
||||
public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7;
|
||||
/** Dim colors. Also known as faint or half intensity. */
|
||||
public final static int CHARACTER_ATTRIBUTE_DIM = 1 << 8;
|
||||
|
||||
public final static int COLOR_INDEX_FOREGROUND = 256;
|
||||
public final static int COLOR_INDEX_BACKGROUND = 257;
|
||||
public final static int COLOR_INDEX_CURSOR = 258;
|
||||
public final static int COLOR_INDEX_FOREGROUND = 256;
|
||||
public final static int COLOR_INDEX_BACKGROUND = 257;
|
||||
public final static int COLOR_INDEX_CURSOR = 258;
|
||||
|
||||
/** The 256 standard color entries and the three special (foreground, background and cursor) ones. */
|
||||
public final static int NUM_INDEXED_COLORS = 259;
|
||||
/** The 256 standard color entries and the three special (foreground, background and cursor) ones. */
|
||||
public final static int NUM_INDEXED_COLORS = 259;
|
||||
|
||||
/** Normal foreground and background colors and no effects. */
|
||||
final static int NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0);
|
||||
/** Normal foreground and background colors and no effects. */
|
||||
final static int NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0);
|
||||
|
||||
static int encode(int foreColor, int backColor, int effect) {
|
||||
return ((effect & 0b111111111) << 18) | ((foreColor & 0b111111111) << 9) | (backColor & 0b111111111);
|
||||
}
|
||||
static int encode(int foreColor, int backColor, int effect) {
|
||||
return ((effect & 0b111111111) << 18) | ((foreColor & 0b111111111) << 9) | (backColor & 0b111111111);
|
||||
}
|
||||
|
||||
public static int decodeForeColor(int encodedColor) {
|
||||
return (encodedColor >> 9) & 0b111111111;
|
||||
}
|
||||
public static int decodeForeColor(int encodedColor) {
|
||||
return (encodedColor >> 9) & 0b111111111;
|
||||
}
|
||||
|
||||
public static int decodeBackColor(int encodedColor) {
|
||||
return encodedColor & 0b111111111;
|
||||
}
|
||||
public static int decodeBackColor(int encodedColor) {
|
||||
return encodedColor & 0b111111111;
|
||||
}
|
||||
|
||||
public static int decodeEffect(int encodedColor) {
|
||||
return (encodedColor >> 18) & 0b111111111;
|
||||
}
|
||||
public static int decodeEffect(int encodedColor) {
|
||||
return (encodedColor >> 18) & 0b111111111;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,107 +2,107 @@ package com.termux.terminal;
|
|||
|
||||
/**
|
||||
* wcwidth() implementation from http://git.musl-libc.org/cgit/musl/tree/src/ctype
|
||||
*
|
||||
* <p/>
|
||||
* Modified to return 0 instead of -1.
|
||||
*/
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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, };
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,};
|
||||
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
/** Return the terminal display width of a code point: 0, 1 or 2. */
|
||||
public static int width(int wc) {
|
||||
if (wc < 0xff) return (wc + 1 & 0x7f) >= 0x21 ? 1 : (wc != 0) ? 0 : 0;
|
||||
if ((wc & 0xfffeffff) < 0xfffe) {
|
||||
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;
|
||||
return 1;
|
||||
}
|
||||
if ((wc & 0xfffe) == 0xfffe) return 0;
|
||||
if (wc - 0x20000 < 0x20000) return 2;
|
||||
if (wc == 0xe0001 || wc - 0xe0020 < 0x5f || wc - 0xe0100 < 0xef) return 0;
|
||||
return 1;
|
||||
}
|
||||
/** Return the terminal display width of a code point: 0, 1 or 2. */
|
||||
public static int width(int wc) {
|
||||
if (wc < 0xff) return (wc + 1 & 0x7f) >= 0x21 ? 1 : (wc != 0) ? 0 : 0;
|
||||
if ((wc & 0xfffeffff) < 0xfffe) {
|
||||
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;
|
||||
return 1;
|
||||
}
|
||||
if ((wc & 0xfffe) == 0xfffe) return 0;
|
||||
if (wc - 0x20000 < 0x20000) return 2;
|
||||
if (wc == 0xe0001 || wc - 0xe0020 < 0x5f || wc - 0xe0100 < 0xef) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** The width at an index position in a java char array. */
|
||||
public static int width(char[] chars, int index) {
|
||||
char c = chars[index];
|
||||
return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c);
|
||||
}
|
||||
/** The width at an index position in a java char array. */
|
||||
public static int width(char[] chars, int index) {
|
||||
char c = chars[index];
|
||||
return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,104 +8,104 @@ import android.view.ScaleGestureDetector;
|
|||
/** A combination of {@link GestureDetector} and {@link ScaleGestureDetector}. */
|
||||
public final class GestureAndScaleRecognizer {
|
||||
|
||||
public interface Listener {
|
||||
boolean onSingleTapUp(MotionEvent e);
|
||||
public interface Listener {
|
||||
boolean onSingleTapUp(MotionEvent e);
|
||||
|
||||
boolean onDoubleTap(MotionEvent e);
|
||||
boolean onDoubleTap(MotionEvent e);
|
||||
|
||||
boolean onScroll(MotionEvent e2, float dx, float dy);
|
||||
boolean onScroll(MotionEvent e2, float dx, float dy);
|
||||
|
||||
boolean onFling(MotionEvent e, float velocityX, float velocityY);
|
||||
boolean onFling(MotionEvent e, float velocityX, float velocityY);
|
||||
|
||||
boolean onScale(float focusX, float focusY, float scale);
|
||||
boolean onScale(float focusX, float focusY, float scale);
|
||||
|
||||
boolean onDown(float x, float y);
|
||||
boolean onDown(float x, float y);
|
||||
|
||||
boolean onUp(MotionEvent e);
|
||||
boolean onUp(MotionEvent e);
|
||||
|
||||
void onLongPress(MotionEvent e);
|
||||
}
|
||||
void onLongPress(MotionEvent e);
|
||||
}
|
||||
|
||||
private final GestureDetector mGestureDetector;
|
||||
private final ScaleGestureDetector mScaleDetector;
|
||||
final Listener mListener;
|
||||
boolean isAfterLongPress;
|
||||
private final GestureDetector mGestureDetector;
|
||||
private final ScaleGestureDetector mScaleDetector;
|
||||
final Listener mListener;
|
||||
boolean isAfterLongPress;
|
||||
|
||||
public GestureAndScaleRecognizer(Context context, Listener listener) {
|
||||
mListener = listener;
|
||||
public GestureAndScaleRecognizer(Context context, Listener listener) {
|
||||
mListener = listener;
|
||||
|
||||
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) {
|
||||
return mListener.onScroll(e2, dx, dy);
|
||||
}
|
||||
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) {
|
||||
return mListener.onScroll(e2, dx, dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
return mListener.onFling(e2, velocityX, velocityY);
|
||||
}
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
return mListener.onFling(e2, velocityX, velocityY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
return mListener.onDown(e.getX(), e.getY());
|
||||
}
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
return mListener.onDown(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
mListener.onLongPress(e);
|
||||
isAfterLongPress = true;
|
||||
}
|
||||
}, null, true /* ignoreMultitouch */);
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
mListener.onLongPress(e);
|
||||
isAfterLongPress = true;
|
||||
}
|
||||
}, null, true /* ignoreMultitouch */);
|
||||
|
||||
mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
return mListener.onSingleTapUp(e);
|
||||
}
|
||||
mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
return mListener.onSingleTapUp(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
return mListener.onDoubleTap(e);
|
||||
}
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
return mListener.onDoubleTap(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTapEvent(MotionEvent e) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public boolean onDoubleTapEvent(MotionEvent e) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
mScaleDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||
return true;
|
||||
}
|
||||
mScaleDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
return mListener.onScale(detector.getFocusX(), detector.getFocusY(), detector.getScaleFactor());
|
||||
}
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
return mListener.onScale(detector.getFocusX(), detector.getFocusY(), detector.getScaleFactor());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onTouchEvent(MotionEvent event) {
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
mScaleDetector.onTouchEvent(event);
|
||||
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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
public void onTouchEvent(MotionEvent event) {
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
mScaleDetector.onTouchEvent(event);
|
||||
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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInProgress() {
|
||||
return mScaleDetector.isInProgress();
|
||||
}
|
||||
public boolean isInProgress() {
|
||||
return mScaleDetector.isInProgress();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,20 +9,20 @@ import com.termux.terminal.TerminalSession;
|
|||
/**
|
||||
* Input and scale listener which may be set on a {@link TerminalView} through
|
||||
* {@link TerminalView#setOnKeyListener(TerminalKeyListener)}.
|
||||
*
|
||||
* <p/>
|
||||
* TODO: Rename to TerminalViewClient.
|
||||
*/
|
||||
public interface TerminalKeyListener {
|
||||
|
||||
/** Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}. */
|
||||
float onScale(float scale);
|
||||
/** Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}. */
|
||||
float onScale(float scale);
|
||||
|
||||
/** On a single tap on the terminal if terminal mouse reporting not enabled. */
|
||||
void onSingleTapUp(MotionEvent e);
|
||||
/** On a single tap on the terminal if terminal mouse reporting not enabled. */
|
||||
void onSingleTapUp(MotionEvent e);
|
||||
|
||||
boolean shouldBackButtonBeMappedToEscape();
|
||||
boolean shouldBackButtonBeMappedToEscape();
|
||||
|
||||
void copyModeChanged(boolean copyMode);
|
||||
void copyModeChanged(boolean copyMode);
|
||||
|
||||
boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session);
|
||||
|
||||
|
|
|
@ -13,223 +13,213 @@ import com.termux.terminal.WcWidth;
|
|||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
final class TerminalRenderer {
|
||||
|
||||
final int mTextSize;
|
||||
final Typeface mTypeface;
|
||||
private final Paint mTextPaint = new Paint();
|
||||
final int mTextSize;
|
||||
final Typeface mTypeface;
|
||||
private final Paint mTextPaint = new Paint();
|
||||
|
||||
/** The width of a single mono spaced character obtained by {@link Paint#measureText(String)} on a single 'X'. */
|
||||
final float mFontWidth;
|
||||
/** The {@link Paint#getFontSpacing()}. See http://www.fampennings.nl/maarten/android/08numgrid/font.png */
|
||||
final int mFontLineSpacing;
|
||||
/** The {@link Paint#ascent()}. See http://www.fampennings.nl/maarten/android/08numgrid/font.png */
|
||||
private final int mFontAscent;
|
||||
/** The {@link #mFontLineSpacing} + {@link #mFontAscent}. */
|
||||
final int mFontLineSpacingAndAscent;
|
||||
/** The width of a single mono spaced character obtained by {@link Paint#measureText(String)} on a single 'X'. */
|
||||
final float mFontWidth;
|
||||
/** The {@link Paint#getFontSpacing()}. See http://www.fampennings.nl/maarten/android/08numgrid/font.png */
|
||||
final int mFontLineSpacing;
|
||||
/** The {@link Paint#ascent()}. See http://www.fampennings.nl/maarten/android/08numgrid/font.png */
|
||||
private final int mFontAscent;
|
||||
/** The {@link #mFontLineSpacing} + {@link #mFontAscent}. */
|
||||
final int mFontLineSpacingAndAscent;
|
||||
|
||||
private final float[] asciiMeasures = new float[127];
|
||||
private final float[] asciiMeasures = new float[127];
|
||||
|
||||
public TerminalRenderer(int textSize, Typeface typeface) {
|
||||
mTextSize = textSize;
|
||||
mTypeface = typeface;
|
||||
public TerminalRenderer(int textSize, Typeface typeface) {
|
||||
mTextSize = textSize;
|
||||
mTypeface = typeface;
|
||||
|
||||
mTextPaint.setTypeface(typeface);
|
||||
mTextPaint.setAntiAlias(true);
|
||||
mTextPaint.setTextSize(textSize);
|
||||
mTextPaint.setTypeface(typeface);
|
||||
mTextPaint.setAntiAlias(true);
|
||||
mTextPaint.setTextSize(textSize);
|
||||
|
||||
mFontLineSpacing = (int) Math.ceil(mTextPaint.getFontSpacing());
|
||||
mFontAscent = (int) Math.ceil(mTextPaint.ascent());
|
||||
mFontLineSpacingAndAscent = mFontLineSpacing + mFontAscent;
|
||||
mFontWidth = mTextPaint.measureText("X");
|
||||
mFontLineSpacing = (int) Math.ceil(mTextPaint.getFontSpacing());
|
||||
mFontAscent = (int) Math.ceil(mTextPaint.ascent());
|
||||
mFontLineSpacingAndAscent = mFontLineSpacing + mFontAscent;
|
||||
mFontWidth = mTextPaint.measureText("X");
|
||||
|
||||
StringBuilder sb = new StringBuilder(" ");
|
||||
for (int i = 0; i < asciiMeasures.length; i++) {
|
||||
sb.setCharAt(0, (char) i);
|
||||
asciiMeasures[i] = mTextPaint.measureText(sb, 0, 1);
|
||||
}
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(" ");
|
||||
for (int i = 0; i < asciiMeasures.length; i++) {
|
||||
sb.setCharAt(0, (char) i);
|
||||
asciiMeasures[i] = mTextPaint.measureText(sb, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/** Render the terminal to a canvas with at a specified row scroll, and an optional rectangular selection. */
|
||||
public final void render(TerminalEmulator mEmulator, Canvas canvas, int topRow, int selectionY1, int selectionY2, int selectionX1, int selectionX2) {
|
||||
final boolean reverseVideo = mEmulator.isReverseVideo();
|
||||
final int endRow = topRow + mEmulator.mRows;
|
||||
final int columns = mEmulator.mColumns;
|
||||
final int cursorCol = mEmulator.getCursorCol();
|
||||
final int cursorRow = mEmulator.getCursorRow();
|
||||
final boolean cursorVisible = mEmulator.isShowingCursor();
|
||||
final TerminalBuffer screen = mEmulator.getScreen();
|
||||
final int[] palette = mEmulator.mColors.mCurrentColors;
|
||||
/** Render the terminal to a canvas with at a specified row scroll, and an optional rectangular selection. */
|
||||
public final void render(TerminalEmulator mEmulator, Canvas canvas, int topRow, int selectionY1, int selectionY2, int selectionX1, int selectionX2) {
|
||||
final boolean reverseVideo = mEmulator.isReverseVideo();
|
||||
final int endRow = topRow + mEmulator.mRows;
|
||||
final int columns = mEmulator.mColumns;
|
||||
final int cursorCol = mEmulator.getCursorCol();
|
||||
final int cursorRow = mEmulator.getCursorRow();
|
||||
final boolean cursorVisible = mEmulator.isShowingCursor();
|
||||
final TerminalBuffer screen = mEmulator.getScreen();
|
||||
final int[] palette = mEmulator.mColors.mCurrentColors;
|
||||
|
||||
if (reverseVideo) canvas.drawColor(palette[TextStyle.COLOR_INDEX_FOREGROUND], PorterDuff.Mode.SRC);
|
||||
if (reverseVideo)
|
||||
canvas.drawColor(palette[TextStyle.COLOR_INDEX_FOREGROUND], PorterDuff.Mode.SRC);
|
||||
|
||||
float heightOffset = mFontLineSpacingAndAscent;
|
||||
for (int row = topRow; row < endRow; row++) {
|
||||
heightOffset += mFontLineSpacing;
|
||||
float heightOffset = mFontLineSpacingAndAscent;
|
||||
for (int row = topRow; row < endRow; row++) {
|
||||
heightOffset += mFontLineSpacing;
|
||||
|
||||
final int cursorX = (row == cursorRow && cursorVisible) ? cursorCol : -1;
|
||||
int selx1 = -1, selx2 = -1;
|
||||
if (row >= selectionY1 && row <= selectionY2) {
|
||||
if (row == selectionY1) selx1 = selectionX1;
|
||||
selx2 = (row == selectionY2) ? selectionX2 : mEmulator.mColumns;
|
||||
}
|
||||
final int cursorX = (row == cursorRow && cursorVisible) ? cursorCol : -1;
|
||||
int selx1 = -1, selx2 = -1;
|
||||
if (row >= selectionY1 && row <= selectionY2) {
|
||||
if (row == selectionY1) selx1 = selectionX1;
|
||||
selx2 = (row == selectionY2) ? selectionX2 : mEmulator.mColumns;
|
||||
}
|
||||
|
||||
TerminalRow lineObject = screen.allocateFullLineIfNecessary(screen.externalToInternalRow(row));
|
||||
final char[] line = lineObject.mText;
|
||||
final int charsUsedInLine = lineObject.getSpaceUsed();
|
||||
TerminalRow lineObject = screen.allocateFullLineIfNecessary(screen.externalToInternalRow(row));
|
||||
final char[] line = lineObject.mText;
|
||||
final int charsUsedInLine = lineObject.getSpaceUsed();
|
||||
|
||||
int lastRunStyle = 0;
|
||||
boolean lastRunInsideCursor = false;
|
||||
int lastRunStartColumn = -1;
|
||||
int lastRunStartIndex = 0;
|
||||
boolean lastRunFontWidthMismatch = false;
|
||||
int currentCharIndex = 0;
|
||||
float measuredWidthForRun = 0.f;
|
||||
int lastRunStyle = 0;
|
||||
boolean lastRunInsideCursor = false;
|
||||
int lastRunStartColumn = -1;
|
||||
int lastRunStartIndex = 0;
|
||||
boolean lastRunFontWidthMismatch = false;
|
||||
int currentCharIndex = 0;
|
||||
float measuredWidthForRun = 0.f;
|
||||
|
||||
for (int column = 0; column < columns;) {
|
||||
final char charAtIndex = line[currentCharIndex];
|
||||
final boolean charIsHighsurrogate = Character.isHighSurrogate(charAtIndex);
|
||||
final int charsForCodePoint = charIsHighsurrogate ? 2 : 1;
|
||||
final int codePoint = charIsHighsurrogate ? Character.toCodePoint(charAtIndex, line[currentCharIndex + 1]) : charAtIndex;
|
||||
final int codePointWcWidth = WcWidth.width(codePoint);
|
||||
final boolean insideCursor = (column >= selx1 && column <= selx2) || (cursorX == column || (codePointWcWidth == 2 && cursorX == column + 1));
|
||||
final int style = lineObject.getStyle(column);
|
||||
for (int column = 0; column < columns; ) {
|
||||
final char charAtIndex = line[currentCharIndex];
|
||||
final boolean charIsHighsurrogate = Character.isHighSurrogate(charAtIndex);
|
||||
final int charsForCodePoint = charIsHighsurrogate ? 2 : 1;
|
||||
final int codePoint = charIsHighsurrogate ? Character.toCodePoint(charAtIndex, line[currentCharIndex + 1]) : charAtIndex;
|
||||
final int codePointWcWidth = WcWidth.width(codePoint);
|
||||
final boolean insideCursor = (column >= selx1 && column <= selx2) || (cursorX == column || (codePointWcWidth == 2 && cursorX == column + 1));
|
||||
final int style = lineObject.getStyle(column);
|
||||
|
||||
// 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
|
||||
// smileys which android font renders as wide.
|
||||
// If this is detected, we draw this code point scaled to match what wcwidth() expects.
|
||||
final float measuredCodePointWidth = (codePoint < asciiMeasures.length) ? asciiMeasures[codePoint] : mTextPaint.measureText(line,
|
||||
currentCharIndex, charsForCodePoint);
|
||||
final boolean fontWidthMismatch = Math.abs(measuredCodePointWidth / mFontWidth - codePointWcWidth) > 0.01;
|
||||
// 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
|
||||
// smileys which android font renders as wide.
|
||||
// If this is detected, we draw this code point scaled to match what wcwidth() expects.
|
||||
final float measuredCodePointWidth = (codePoint < asciiMeasures.length) ? asciiMeasures[codePoint] : mTextPaint.measureText(line,
|
||||
currentCharIndex, charsForCodePoint);
|
||||
final boolean fontWidthMismatch = Math.abs(measuredCodePointWidth / mFontWidth - codePointWcWidth) > 0.01;
|
||||
|
||||
if (style != lastRunStyle || insideCursor != lastRunInsideCursor || fontWidthMismatch || lastRunFontWidthMismatch) {
|
||||
if (column == 0) {
|
||||
// Skip first column as there is nothing to draw, just record the current style.
|
||||
} else {
|
||||
final int columnWidthSinceLastRun = column - lastRunStartColumn;
|
||||
final int charsSinceLastRun = currentCharIndex - lastRunStartIndex;
|
||||
drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, lastRunStartIndex, charsSinceLastRun,
|
||||
measuredWidthForRun, lastRunInsideCursor, lastRunStyle, reverseVideo);
|
||||
}
|
||||
measuredWidthForRun = 0.f;
|
||||
lastRunStyle = style;
|
||||
lastRunInsideCursor = insideCursor;
|
||||
lastRunStartColumn = column;
|
||||
lastRunStartIndex = currentCharIndex;
|
||||
lastRunFontWidthMismatch = fontWidthMismatch;
|
||||
}
|
||||
measuredWidthForRun += measuredCodePointWidth;
|
||||
column += codePointWcWidth;
|
||||
currentCharIndex += charsForCodePoint;
|
||||
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,
|
||||
// instead of e.g. being considered inside the cursor in the next run.
|
||||
currentCharIndex += Character.isHighSurrogate(line[currentCharIndex]) ? 2 : 1;
|
||||
}
|
||||
}
|
||||
if (style != lastRunStyle || insideCursor != lastRunInsideCursor || fontWidthMismatch || lastRunFontWidthMismatch) {
|
||||
if (column == 0) {
|
||||
// Skip first column as there is nothing to draw, just record the current style.
|
||||
} else {
|
||||
final int columnWidthSinceLastRun = column - lastRunStartColumn;
|
||||
final int charsSinceLastRun = currentCharIndex - lastRunStartIndex;
|
||||
drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, lastRunStartIndex, charsSinceLastRun,
|
||||
measuredWidthForRun, lastRunInsideCursor, lastRunStyle, reverseVideo);
|
||||
}
|
||||
measuredWidthForRun = 0.f;
|
||||
lastRunStyle = style;
|
||||
lastRunInsideCursor = insideCursor;
|
||||
lastRunStartColumn = column;
|
||||
lastRunStartIndex = currentCharIndex;
|
||||
lastRunFontWidthMismatch = fontWidthMismatch;
|
||||
}
|
||||
measuredWidthForRun += measuredCodePointWidth;
|
||||
column += codePointWcWidth;
|
||||
currentCharIndex += charsForCodePoint;
|
||||
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,
|
||||
// instead of e.g. being considered inside the cursor in the next run.
|
||||
currentCharIndex += Character.isHighSurrogate(line[currentCharIndex]) ? 2 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
final int columnWidthSinceLastRun = columns - lastRunStartColumn;
|
||||
final int charsSinceLastRun = currentCharIndex - lastRunStartIndex;
|
||||
drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, lastRunStartIndex, charsSinceLastRun,
|
||||
measuredWidthForRun, lastRunInsideCursor, lastRunStyle, reverseVideo);
|
||||
}
|
||||
}
|
||||
final int columnWidthSinceLastRun = columns - lastRunStartColumn;
|
||||
final int charsSinceLastRun = currentCharIndex - lastRunStartIndex;
|
||||
drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, lastRunStartIndex, charsSinceLastRun,
|
||||
measuredWidthForRun, lastRunInsideCursor, lastRunStyle, reverseVideo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param canvas
|
||||
* the canvas to render on
|
||||
* @param palette
|
||||
* the color palette to look up colors from textStyle
|
||||
* @param y
|
||||
* height offset into the canvas where to render the line: line * {@link #mFontLineSpacing}
|
||||
* @param startColumn
|
||||
* the run offset in columns
|
||||
* @param runWidthColumns
|
||||
* the run width in columns - this is computed from wcwidth() and may not be what the font measures to
|
||||
* @param text
|
||||
* 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,
|
||||
float mes, boolean cursor, int textStyle, boolean reverseVideo) {
|
||||
int foreColor = TextStyle.decodeForeColor(textStyle);
|
||||
int backColor = TextStyle.decodeBackColor(textStyle);
|
||||
final int effect = TextStyle.decodeEffect(textStyle);
|
||||
float left = startColumn * mFontWidth;
|
||||
float right = left + runWidthColumns * mFontWidth;
|
||||
/**
|
||||
* @param canvas the canvas to render on
|
||||
* @param palette the color palette to look up colors from textStyle
|
||||
* @param y height offset into the canvas where to render the line: line * {@link #mFontLineSpacing}
|
||||
* @param startColumn the run offset in columns
|
||||
* @param runWidthColumns the run width in columns - this is computed from wcwidth() and may not be what the font measures to
|
||||
* @param text 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,
|
||||
float mes, boolean cursor, int textStyle, boolean reverseVideo) {
|
||||
int foreColor = TextStyle.decodeForeColor(textStyle);
|
||||
int backColor = TextStyle.decodeBackColor(textStyle);
|
||||
final int effect = TextStyle.decodeEffect(textStyle);
|
||||
float left = startColumn * mFontWidth;
|
||||
float right = left + runWidthColumns * mFontWidth;
|
||||
|
||||
mes = mes / mFontWidth;
|
||||
boolean savedMatrix = false;
|
||||
if (Math.abs(mes - runWidthColumns) > 0.01) {
|
||||
canvas.save();
|
||||
canvas.scale(runWidthColumns / mes, 1.f);
|
||||
left *= mes / runWidthColumns;
|
||||
right *= mes / runWidthColumns;
|
||||
savedMatrix = true;
|
||||
}
|
||||
mes = mes / mFontWidth;
|
||||
boolean savedMatrix = false;
|
||||
if (Math.abs(mes - runWidthColumns) > 0.01) {
|
||||
canvas.save();
|
||||
canvas.scale(runWidthColumns / mes, 1.f);
|
||||
left *= mes / runWidthColumns;
|
||||
right *= mes / runWidthColumns;
|
||||
savedMatrix = true;
|
||||
}
|
||||
|
||||
// Reverse video here if _one and only one_ of the reverse flags are set:
|
||||
boolean reverseVideoHere = reverseVideo ^ (effect & (TextStyle.CHARACTER_ATTRIBUTE_INVERSE)) != 0;
|
||||
// Switch if _one and only one_ of reverse video and cursor is set:
|
||||
if (reverseVideoHere ^ cursor) {
|
||||
int tmp = foreColor;
|
||||
foreColor = backColor;
|
||||
backColor = tmp;
|
||||
}
|
||||
// Reverse video here if _one and only one_ of the reverse flags are set:
|
||||
boolean reverseVideoHere = reverseVideo ^ (effect & (TextStyle.CHARACTER_ATTRIBUTE_INVERSE)) != 0;
|
||||
// Switch if _one and only one_ of reverse video and cursor is set:
|
||||
if (reverseVideoHere ^ cursor) {
|
||||
int tmp = foreColor;
|
||||
foreColor = backColor;
|
||||
backColor = tmp;
|
||||
}
|
||||
|
||||
if (backColor != TextStyle.COLOR_INDEX_BACKGROUND) {
|
||||
// Only draw non-default background.
|
||||
mTextPaint.setColor(palette[backColor]);
|
||||
canvas.drawRect(left, y - mFontLineSpacingAndAscent + mFontAscent, right, y, mTextPaint);
|
||||
}
|
||||
if (backColor != TextStyle.COLOR_INDEX_BACKGROUND) {
|
||||
// Only draw non-default background.
|
||||
mTextPaint.setColor(palette[backColor]);
|
||||
canvas.drawRect(left, y - mFontLineSpacingAndAscent + mFontAscent, right, y, mTextPaint);
|
||||
}
|
||||
|
||||
if ((effect & TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE) == 0) {
|
||||
// Treat blink as bold:
|
||||
final boolean bold = (effect & (TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_BLINK)) != 0;
|
||||
final boolean underline = (effect & TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE) != 0;
|
||||
final boolean italic = (effect & TextStyle.CHARACTER_ATTRIBUTE_ITALIC) != 0;
|
||||
final boolean strikeThrough = (effect & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0;
|
||||
final boolean dim = (effect & TextStyle.CHARACTER_ATTRIBUTE_DIM) != 0;
|
||||
if ((effect & TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE) == 0) {
|
||||
// Treat blink as bold:
|
||||
final boolean bold = (effect & (TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_BLINK)) != 0;
|
||||
final boolean underline = (effect & TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE) != 0;
|
||||
final boolean italic = (effect & TextStyle.CHARACTER_ATTRIBUTE_ITALIC) != 0;
|
||||
final boolean strikeThrough = (effect & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0;
|
||||
final boolean dim = (effect & TextStyle.CHARACTER_ATTRIBUTE_DIM) != 0;
|
||||
|
||||
// Let bold have bright colors if applicable (one of the first 8):
|
||||
final int actualForeColor = foreColor + (bold && foreColor < 8 ? 8 : 0);
|
||||
// Let bold have bright colors if applicable (one of the first 8):
|
||||
final int actualForeColor = foreColor + (bold && foreColor < 8 ? 8 : 0);
|
||||
|
||||
int foreColorARGB = palette[actualForeColor];
|
||||
if (dim) {
|
||||
int red = (0xFF & (foreColorARGB >> 16));
|
||||
int green = (0xFF & (foreColorARGB >> 8));
|
||||
int blue = (0xFF & foreColorARGB);
|
||||
// Dim color handling used by libvte which in turn took it from xterm
|
||||
// (https://bug735245.bugzilla-attachments.gnome.org/attachment.cgi?id=284267):
|
||||
red = red * 2 / 3;
|
||||
green = green * 2 / 3;
|
||||
blue = blue * 2 / 3;
|
||||
foreColorARGB = 0xFF000000 + (red << 16) + (green << 8) + blue;
|
||||
}
|
||||
int foreColorARGB = palette[actualForeColor];
|
||||
if (dim) {
|
||||
int red = (0xFF & (foreColorARGB >> 16));
|
||||
int green = (0xFF & (foreColorARGB >> 8));
|
||||
int blue = (0xFF & foreColorARGB);
|
||||
// Dim color handling used by libvte which in turn took it from xterm
|
||||
// (https://bug735245.bugzilla-attachments.gnome.org/attachment.cgi?id=284267):
|
||||
red = red * 2 / 3;
|
||||
green = green * 2 / 3;
|
||||
blue = blue * 2 / 3;
|
||||
foreColorARGB = 0xFF000000 + (red << 16) + (green << 8) + blue;
|
||||
}
|
||||
|
||||
mTextPaint.setFakeBoldText(bold);
|
||||
mTextPaint.setUnderlineText(underline);
|
||||
mTextPaint.setTextSkewX(italic ? -0.35f : 0.f);
|
||||
mTextPaint.setStrikeThruText(strikeThrough);
|
||||
mTextPaint.setColor(foreColorARGB);
|
||||
mTextPaint.setFakeBoldText(bold);
|
||||
mTextPaint.setUnderlineText(underline);
|
||||
mTextPaint.setTextSkewX(italic ? -0.35f : 0.f);
|
||||
mTextPaint.setStrikeThruText(strikeThrough);
|
||||
mTextPaint.setColor(foreColorARGB);
|
||||
|
||||
// The text alignment is the default Paint.Align.LEFT.
|
||||
canvas.drawText(text, startCharIndex, runWidthChars, left, y - mFontLineSpacingAndAscent, mTextPaint);
|
||||
}
|
||||
// The text alignment is the default Paint.Align.LEFT.
|
||||
canvas.drawText(text, startCharIndex, runWidthChars, left, y - mFontLineSpacingAndAscent, mTextPaint);
|
||||
}
|
||||
|
||||
if (savedMatrix) canvas.restore();
|
||||
}
|
||||
if (savedMatrix) canvas.restore();
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user