Compare commits

..

21 Commits
v0.23 ... v0.26

Author SHA1 Message Date
Fredrik Fornwall
19eb371d23 Do not force soft keyboard visible when hw exists 2016-01-13 13:17:53 +01:00
Fredrik Fornwall
6caaae4fd6 Do not start text selection directly on LMB 2016-01-13 13:17:36 +01:00
Fredrik Fornwall
ed544102bc Show icons for copy and paste menu items 2016-01-13 12:28:31 +01:00
Fredrik Fornwall
8f1ab1bc17 Use action mode overlay on pre-6.0 devices
This avoids the terminal content from being pushed down when starting
text selection. The drawback is that one cannot select text at the
top rows without scrolling - something to fix for the future.
2016-01-13 12:27:15 +01:00
Fredrik Fornwall
50337cbf9d Fix gesture handling while selecting text
Also remove stray debug logging.
2016-01-13 10:52:23 +01:00
Fredrik Fornwall
fa9ea2db5c Do not auto scroll when selecting text 2016-01-13 04:20:36 +01:00
Fredrik Fornwall
7a659ebd21 Improve session name dialog
- Show keyboard directly.
- Let return create the session.
2016-01-13 03:44:03 +01:00
Fredrik Fornwall
54bc1ed791 Do not recognize gestures while selecting text 2016-01-13 03:42:46 +01:00
Fredrik Fornwall
fe4365c94b Simplify long press on new session button 2016-01-13 03:18:22 +01:00
Fredrik Fornwall
0c13ea1bd4 Bump version to 0.26 2016-01-13 03:02:09 +01:00
Fredrik Fornwall
862b461a07 Improve text selection functionality
- Make text selection easier and quicker by selecting text directly on long press, and using standard grip bars for changing the selection.
- Disable the drawer while selecting text.
- Fix problem with selecting snippets of text with wide unicode characters at start and end.
- Remove the "tap-screen" configuration option for a more common show keyboard behaviour when tapping the terminal.
- Do no longer map the back key to escape by default - but it's still possible to do by configuration.
- Add new hardware keyboard shortcut Ctrl+Shift+K for toggling soft keyboard visibility.
2016-01-13 03:01:29 +01:00
Fredrik Fornwall
845976be0f Update README.md 2016-01-13 01:29:57 +01:00
Fredrik Fornwall
60bdaa3bf6 Add test for space handling 2016-01-05 03:14:18 +01:00
Fredrik Fornwall
eeb873f4e4 Do not save instance state in DrawerLayout
This was not needed, and due to missing CREATOR field caused a crash
after returning to the activity after it was evicted
2016-01-05 01:00:25 +01:00
Fredrik Fornwall
207ddf9fdc Change member to local variable 2016-01-05 00:48:16 +01:00
Fredrik Fornwall
5ca82ea095 Bump version to 0.25 2015-12-30 00:49:35 +01:00
Fredrik Fornwall
913c474d32 Input normal ^ even on other unicode char input
Some bluetooth keyboards [1] input U+02C6, the unicode character
MODIFIER LETTER CIRCUMFLEX ACCENT instead of the more common ^
(U+005E CIRCUMFLEX ACCENT). Remap it to the common caret since
that is what terminal programs expect.

[1] https://plus.google.com/100972300636796512022/posts/f7PKpXWesgG
2015-12-30 00:46:08 +01:00
Fredrik Fornwall
657c270d97 Fix error message 2015-12-28 20:49:46 +01:00
Fredrik Fornwall
7763931035 Bump app version to 0.24 2015-12-28 20:48:13 +01:00
Fredrik Fornwall
50005bc794 Fix problem with font and color loading at startup
Using View#post() does not work in onCreate().
2015-12-28 20:47:34 +01:00
Fredrik Fornwall
96f5ed985a Remove unused files 2015-12-28 02:30:28 +01:00
20 changed files with 508 additions and 495 deletions

View File

@@ -1,6 +1,7 @@
Termux app Termux app
========== ==========
[![Travis build status](https://travis-ci.org/termux/termux-app.svg?branch=master)](https://travis-ci.org/termux/termux-app) [![Travis build status](https://travis-ci.org/termux/termux-app.svg?branch=master)](https://travis-ci.org/termux/termux-app)
[![Join the chat at https://gitter.im/termux/termux](https://badges.gitter.im/termux/termux.svg)](https://gitter.im/termux/termux)
Termux is an Android terminal app and Linux environment. Termux is an Android terminal app and Linux environment.

View File

@@ -9,17 +9,17 @@ android {
} }
sourceSets { sourceSets {
main { main {
jni.srcDirs = [] jni.srcDirs = []
} }
} }
defaultConfig { defaultConfig {
applicationId "com.termux" applicationId "com.termux"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 23 targetSdkVersion 23
versionCode 23 versionCode 26
versionName "0.23" versionName "0.26"
} }
buildTypes { buildTypes {

View File

@@ -3,10 +3,15 @@ package com.termux.app;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.res.Configuration;
import android.text.Selection;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView;
final class DialogUtils { final class DialogUtils {
@@ -14,10 +19,25 @@ final class DialogUtils {
void onTextSet(String text); void onTextSet(String text);
} }
static void textInput(Activity activity, int titleText, int positiveButtonText, String initialText, final TextSetListener onPositive) { static void textInput(Activity activity, int titleText, int positiveButtonText, String initialText, final TextSetListener onPositive,
int neutralButtonText, final TextSetListener onNeutral) {
final EditText input = new EditText(activity); final EditText input = new EditText(activity);
input.setSingleLine(); input.setSingleLine();
if (initialText != null) input.setText(initialText); if (initialText != null) {
input.setText(initialText);
Selection.setSelection(input.getText(), initialText.length());
}
final AlertDialog[] dialogHolder = new AlertDialog[1];
input.setImeActionLabel(activity.getResources().getString(positiveButtonText), KeyEvent.KEYCODE_ENTER);
input.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
onPositive.onTextSet(input.getText().toString());
dialogHolder[0].dismiss();
return true;
}
});
float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, activity.getResources().getDisplayMetrics()); float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, activity.getResources().getDisplayMetrics());
// https://www.google.com/design/spec/components/dialogs.html#dialogs-specs // https://www.google.com/design/spec/components/dialogs.html#dialogs-specs
@@ -27,17 +47,34 @@ final class DialogUtils {
LinearLayout layout = new LinearLayout(activity); LinearLayout layout = new LinearLayout(activity);
layout.setOrientation(LinearLayout.VERTICAL); layout.setOrientation(LinearLayout.VERTICAL);
layout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); layout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
// layout.setGravity(Gravity.CLIP_VERTICAL);
layout.setPadding(paddingTopAndSides, paddingTopAndSides, paddingTopAndSides, paddingBottom); layout.setPadding(paddingTopAndSides, paddingTopAndSides, paddingTopAndSides, paddingBottom);
layout.addView(input); layout.addView(input);
new AlertDialog.Builder(activity).setTitle(titleText).setView(layout).setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() { AlertDialog.Builder builder = new AlertDialog.Builder(activity)
@Override .setTitle(titleText).setView(layout)
public void onClick(DialogInterface d, int whichButton) { .setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() {
onPositive.onTextSet(input.getText().toString()); @Override
} public void onClick(DialogInterface d, int whichButton) {
}).setNegativeButton(android.R.string.cancel, null).show(); onPositive.onTextSet(input.getText().toString());
input.requestFocus(); }
})
.setNegativeButton(android.R.string.cancel, null);
if (onNeutral != null) {
builder.setNeutralButton(neutralButtonText, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onNeutral.onTextSet(input.getText().toString());
}
});
}
dialogHolder[0] = builder.create();
if ((activity.getResources().getConfiguration().hardKeyboardHidden & Configuration.HARDKEYBOARDHIDDEN_YES) == 0) {
// Show soft keyboard unless hardware keyboard available.
dialogHolder[0].getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
dialogHolder[0].show();
} }
} }

View File

@@ -82,7 +82,8 @@ import java.util.regex.Pattern;
*/ */
public final class TermuxActivity extends Activity implements ServiceConnection { public final class TermuxActivity extends Activity implements ServiceConnection {
private static final int CONTEXTMENU_SELECT_ID = 0; private static final int CONTEXTMENU_SELECT_URL_ID = 0;
private static final int CONTEXTMENU_SHARE_TRANSCRIPT_ID = 1;
private static final int CONTEXTMENU_PASTE_ID = 3; private static final int CONTEXTMENU_PASTE_ID = 3;
private static final int CONTEXTMENU_KILL_PROCESS_ID = 4; private static final int CONTEXTMENU_KILL_PROCESS_ID = 4;
private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5; private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5;
@@ -122,7 +123,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
*/ */
boolean mIsVisible; boolean mIsVisible;
private SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes( private final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build(); .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
private int mBellSoundId; private int mBellSoundId;
@@ -136,9 +137,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
if (ensureStoragePermissionGranted()) TermuxInstaller.setupStorageSymlinks(TermuxActivity.this); if (ensureStoragePermissionGranted()) TermuxInstaller.setupStorageSymlinks(TermuxActivity.this);
return; return;
} }
if (whatToReload == null || "colors".equals(whatToReload)) mTerminalView.checkForColors(); mTerminalView.checkForFontAndColors();
if (whatToReload == null || "font".equals(whatToReload)) mTerminalView.checkForTypeface(); mSettings.reloadFromProperties(TermuxActivity.this);
if (whatToReload == null || "settings".equals(whatToReload)) mSettings.reloadFromProperties(TermuxActivity.this);
} }
} }
}; };
@@ -219,6 +219,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
getDrawer().closeDrawers(); getDrawer().closeDrawers();
} else if (unicodeChar == 'f'/* full screen */) { } else if (unicodeChar == 'f'/* full screen */) {
toggleImmersive(); toggleImmersive();
} else if (unicodeChar == 'k'/* keyboard */) {
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
} else if (unicodeChar == 'm'/* menu */) { } else if (unicodeChar == 'm'/* menu */) {
mTerminalView.showContextMenu(); mTerminalView.showContextMenu();
} else if (unicodeChar == 'r'/* rename */) { } else if (unicodeChar == 'r'/* rename */) {
@@ -256,66 +259,51 @@ public final class TermuxActivity extends Activity implements ServiceConnection
return scale; return scale;
} }
@Override
public void onLongPress(MotionEvent event) {
mTerminalView.showContextMenu();
}
@Override @Override
public void onSingleTapUp(MotionEvent e) { public void onSingleTapUp(MotionEvent e) {
switch (mSettings.mTapBehaviour) { InputMethodManager mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
case TermuxPreferences.TAP_TOGGLE_KEYBOARD: mgr.showSoftInput(mTerminalView, InputMethodManager.SHOW_IMPLICIT);
// Toggle keyboard visibility if tapping with a finger:
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
break;
case TermuxPreferences.TAP_SHOW_MENU:
mTerminalView.showContextMenu();
break;
}
} }
@Override @Override
public boolean shouldBackButtonBeMappedToEscape() { public boolean shouldBackButtonBeMappedToEscape() {
return mSettings.mBackIsEscape; return mSettings.mBackIsEscape;
} }
@Override
public void copyModeChanged(boolean copyMode) {
// Disable drawer while copying.
getDrawer().setDrawerLockMode(copyMode ? DrawerLayout.LOCK_MODE_LOCKED_CLOSED : DrawerLayout.LOCK_MODE_UNLOCKED);
}
}); });
findViewById(R.id.new_session_button).setOnClickListener(new OnClickListener() { View newSessionButton = findViewById(R.id.new_session_button);
newSessionButton.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
addNewSession(false, null); addNewSession(false, null);
} }
}); });
findViewById(R.id.new_session_button).setOnLongClickListener(new OnLongClickListener() { newSessionButton.setOnLongClickListener(new OnLongClickListener() {
@Override @Override
public boolean onLongClick(View v) { public boolean onLongClick(View v) {
Resources res = getResources(); Resources res = getResources();
new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.new_session) DialogUtils.textInput(TermuxActivity.this, R.string.session_new_named_title, R.string.session_new_named_positive_button, null,
.setItems(new String[] { res.getString(R.string.new_session_normal_unnamed), res.getString(R.string.new_session_normal_named), new DialogUtils.TextSetListener() {
res.getString(R.string.new_session_failsafe) }, new DialogInterface.OnClickListener() { @Override
@Override public void onTextSet(String text) {
public void onClick(DialogInterface dialog, int which) { addNewSession(false, text);
switch (which) { }
case 0: }, R.string.new_session_failsafe, new DialogUtils.TextSetListener() {
addNewSession(false, null); @Override
break; public void onTextSet(String text) {
case 1: addNewSession(true, text);
DialogUtils.textInput(TermuxActivity.this, R.string.session_new_named_title, R.string.session_new_named_positive_button, null,
new DialogUtils.TextSetListener() {
@Override
public void onTextSet(String text) {
addNewSession(false, text);
}
});
break;
case 2:
addNewSession(true, null);
break;
} }
} }
}).show(); );
return true; return true;
} }
}); });
@@ -336,8 +324,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
startService(serviceIntent); startService(serviceIntent);
if (!bindService(serviceIntent, this, 0)) throw new RuntimeException("bindService() failed"); if (!bindService(serviceIntent, this, 0)) throw new RuntimeException("bindService() failed");
mTerminalView.checkForTypeface(); mTerminalView.checkForFontAndColors();
mTerminalView.checkForColors();
mBellSoundId = mBellSoundPool.load(this, R.raw.bell, 1); mBellSoundId = mBellSoundPool.load(this, R.raw.bell, 1);
} }
@@ -389,7 +376,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
@Override @Override
public void onClipboardText(TerminalSession session, String text) { public void onClipboardText(TerminalSession session, String text) {
if (!mIsVisible) return; if (!mIsVisible) return;
showToast("Clipboard set:\n\"" + text + "\"", true); showToast("Clipboard:\n\"" + text + "\"", false);
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(new ClipData(null, new String[] { "text/plain" }, new ClipData.Item(text))); clipboard.setPrimaryClip(new ClipData(null, new String[] { "text/plain" }, new ClipData.Item(text)));
} }
@@ -475,6 +462,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
TermuxInstaller.setupIfNeeded(TermuxActivity.this, new Runnable() { TermuxInstaller.setupIfNeeded(TermuxActivity.this, new Runnable() {
@Override @Override
public void run() { public void run() {
if (mTermService == null) return; // Activity might have been destroyed.
try { try {
if (TermuxPreferences.isShowWelcomeDialog(TermuxActivity.this)) { if (TermuxPreferences.isShowWelcomeDialog(TermuxActivity.this)) {
new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.welcome_dialog_title).setMessage(R.string.welcome_dialog_body) new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.welcome_dialog_title).setMessage(R.string.welcome_dialog_body)
@@ -510,7 +498,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
public void onTextSet(String text) { public void onTextSet(String text) {
sessionToRename.mSessionName = text; sessionToRename.mSessionName = text;
} }
}); }, -1, null);
} }
@Override @Override
@@ -623,9 +611,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
TerminalSession currentSession = getCurrentTermSession(); TerminalSession currentSession = getCurrentTermSession();
if (currentSession == null) return; if (currentSession == null) return;
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); menu.add(Menu.NONE, CONTEXTMENU_SELECT_URL_ID, Menu.NONE, R.string.select_url);
menu.add(Menu.NONE, CONTEXTMENU_PASTE_ID, Menu.NONE, R.string.paste_text).setEnabled(clipboard.hasPrimaryClip()); menu.add(Menu.NONE, CONTEXTMENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.select_all_and_share);
menu.add(Menu.NONE, CONTEXTMENU_SELECT_ID, Menu.NONE, R.string.select);
menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal); menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, R.string.kill_process).setEnabled(currentSession.isRunning()); menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, R.string.kill_process).setEnabled(currentSession.isRunning());
menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_FULLSCREEN_ID, Menu.NONE, R.string.toggle_fullscreen).setCheckable(true).setChecked(mSettings.isFullScreen()); menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_FULLSCREEN_ID, Menu.NONE, R.string.toggle_fullscreen).setCheckable(true).setChecked(mSettings.isFullScreen());
@@ -701,89 +688,74 @@ public final class TermuxActivity extends Activity implements ServiceConnection
@Override @Override
public boolean onContextItemSelected(MenuItem item) { public boolean onContextItemSelected(MenuItem item) {
TerminalSession session = getCurrentTermSession();
switch (item.getItemId()) { switch (item.getItemId()) {
case CONTEXTMENU_SELECT_ID: case CONTEXTMENU_SELECT_URL_ID:
CharSequence[] items = new CharSequence[] { getString(R.string.select_text), getString(R.string.select_url), showUrlSelection();
getString(R.string.select_all_and_share) }; return true;
new AlertDialog.Builder(this).setItems(items, new DialogInterface.OnClickListener() { case CONTEXTMENU_SHARE_TRANSCRIPT_ID:
@Override if (session != null) {
public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(Intent.ACTION_SEND);
switch (which) { intent.setType("text/plain");
case 0: intent.putExtra(Intent.EXTRA_TEXT, session.getEmulator().getScreen().getTranscriptText().trim());
mTerminalView.toggleSelectingText(); intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_transcript_title));
break; startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title)));
case 1: }
showUrlSelection(); return true;
break; case CONTEXTMENU_PASTE_ID:
case 2: doPaste();
TerminalSession session = getCurrentTermSession(); return true;
if (session != null) { case CONTEXTMENU_KILL_PROCESS_ID:
Intent intent = new Intent(Intent.ACTION_SEND); final AlertDialog.Builder b = new AlertDialog.Builder(this);
intent.setType("text/plain"); b.setIcon(android.R.drawable.ic_dialog_alert);
intent.putExtra(Intent.EXTRA_TEXT, session.getEmulator().getScreen().getTranscriptText().trim()); b.setMessage(R.string.confirm_kill_process);
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_transcript_title)); b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title))); @Override
} public void onClick(DialogInterface dialog, int id) {
break; dialog.dismiss();
getCurrentTermSession().finishIfRunning();
} }
dialog.dismiss(); });
b.setNegativeButton(android.R.string.no, null);
b.show();
return true;
case CONTEXTMENU_RESET_TERMINAL_ID: {
if (session != null) {
session.reset();
showToast(getResources().getString(R.string.reset_toast_notification), true);
} }
}).show(); return true;
return true; }
case CONTEXTMENU_PASTE_ID: case CONTEXTMENU_STYLING_ID: {
doPaste(); Intent stylingIntent = new Intent();
return true; stylingIntent.setClassName("com.termux.styling", "com.termux.styling.TermuxStyleActivity");
case CONTEXTMENU_KILL_PROCESS_ID: try {
final AlertDialog.Builder b = new AlertDialog.Builder(this); startActivity(stylingIntent);
b.setIcon(android.R.drawable.ic_dialog_alert); } catch (ActivityNotFoundException e) {
b.setMessage(R.string.confirm_kill_process); new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed)
b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.styling_install, new android.content.DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int which) {
dialog.dismiss(); startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.termux.styling")));
getCurrentTermSession().finishIfRunning(); }
}).setNegativeButton(android.R.string.cancel, null).show();
} }
});
b.setNegativeButton(android.R.string.no, null);
b.show();
return true;
case CONTEXTMENU_RESET_TERMINAL_ID: {
TerminalSession session = getCurrentTermSession();
if (session != null) {
session.reset();
showToast(getResources().getString(R.string.reset_toast_notification), true);
} }
return true; return true;
} case CONTEXTMENU_TOGGLE_FULLSCREEN_ID:
case CONTEXTMENU_STYLING_ID: { toggleImmersive();
Intent stylingIntent = new Intent(); return true;
stylingIntent.setClassName("com.termux.styling", "com.termux.styling.TermuxStyleActivity"); case CONTEXTMENU_HELP_ID:
try { startActivity(new Intent(this, TermuxHelpActivity.class));
startActivity(stylingIntent); return true;
} catch (ActivityNotFoundException e) { default:
new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed) return super.onContextItemSelected(item);
.setPositiveButton(R.string.styling_install, new android.content.DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.termux.styling")));
}
}).setNegativeButton(android.R.string.cancel, null).show();
}
}
return true;
case CONTEXTMENU_TOGGLE_FULLSCREEN_ID:
toggleImmersive();
return true;
case CONTEXTMENU_HELP_ID:
startActivity(new Intent(this, TermuxHelpActivity.class));
return true;
default:
return super.onContextItemSelected(item);
} }
} }
@Override @Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
if (requestCode == REQUESTCODE_PERMISSION_STORAGE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (requestCode == REQUESTCODE_PERMISSION_STORAGE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
TermuxInstaller.setupStorageSymlinks(this); TermuxInstaller.setupStorageSymlinks(this);
} }

View File

@@ -16,7 +16,6 @@ import android.widget.RelativeLayout;
public final class TermuxHelpActivity extends Activity { public final class TermuxHelpActivity extends Activity {
private WebView mWebView; private WebView mWebView;
private ProgressBar mProgressBar;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -25,10 +24,10 @@ public final class TermuxHelpActivity extends Activity {
final RelativeLayout progressLayout = new RelativeLayout(this); final RelativeLayout progressLayout = new RelativeLayout(this);
RelativeLayout.LayoutParams lParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); RelativeLayout.LayoutParams lParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lParams.addRule(RelativeLayout.CENTER_IN_PARENT); lParams.addRule(RelativeLayout.CENTER_IN_PARENT);
mProgressBar = new ProgressBar(this); ProgressBar progressBar = new ProgressBar(this);
mProgressBar.setIndeterminate(true); progressBar.setIndeterminate(true);
mProgressBar.setLayoutParams(lParams); progressBar.setLayoutParams(lParams);
progressLayout.addView(mProgressBar); progressLayout.addView(progressBar);
mWebView = new WebView(this); mWebView = new WebView(this);
WebSettings settings = mWebView.getSettings(); WebSettings settings = mWebView.getSettings();

View File

@@ -1,7 +1,5 @@
package com.termux.app; package com.termux.app;
import com.termux.terminal.TerminalSession;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
@@ -10,6 +8,8 @@ import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.widget.Toast; import android.widget.Toast;
import com.termux.terminal.TerminalSession;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@@ -26,14 +26,6 @@ final class TermuxPreferences {
static final int BELL_BEEP = 2; static final int BELL_BEEP = 2;
static final int BELL_IGNORE = 3; static final int BELL_IGNORE = 3;
@IntDef({TAP_TOGGLE_KEYBOARD, TAP_SHOW_MENU, TAP_IGNORE})
@Retention(RetentionPolicy.SOURCE)
public @interface TapTerminalBehaviour {}
static final int TAP_TOGGLE_KEYBOARD = 1;
static final int TAP_SHOW_MENU = 2;
static final int TAP_IGNORE = 3;
private final int MIN_FONTSIZE; private final int MIN_FONTSIZE;
private static final int MAX_FONTSIZE = 256; private static final int MAX_FONTSIZE = 256;
@@ -48,9 +40,6 @@ final class TermuxPreferences {
@AsciiBellBehaviour @AsciiBellBehaviour
int mBellBehaviour = BELL_VIBRATE; int mBellBehaviour = BELL_VIBRATE;
@TapTerminalBehaviour
int mTapBehaviour = TAP_TOGGLE_KEYBOARD;
boolean mBackIsEscape = true; boolean mBackIsEscape = true;
TermuxPreferences(Context context) { TermuxPreferences(Context context) {
@@ -124,42 +113,26 @@ final class TermuxPreferences {
public void reloadFromProperties(Context context) { public void reloadFromProperties(Context context) {
try { try {
File propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties"); File propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties");
Properties props = new Properties();
if (propsFile.isFile() && propsFile.canRead()) { if (propsFile.isFile() && propsFile.canRead()) {
Properties props = new Properties();
try (FileInputStream in = new FileInputStream(propsFile)) { try (FileInputStream in = new FileInputStream(propsFile)) {
props.load(in); 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("tap-screen", "toggle-keyboard")) {
case "show-menu":
mTapBehaviour = TAP_SHOW_MENU;
break;
case "ignore":
mTapBehaviour = TAP_IGNORE;
break;
default: // "toggle-keyboard".
mTapBehaviour = TAP_TOGGLE_KEYBOARD;
break;
}
mBackIsEscape = !"back".equals(props.getProperty("back-key", "escape"));
} else {
mBellBehaviour = BELL_VIBRATE;
mTapBehaviour = TAP_TOGGLE_KEYBOARD;
mBackIsEscape = true;
} }
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", "escape"));
} catch (Exception e) { } catch (Exception e) {
Toast.makeText(context, "Error loading properties: " + e.getMessage(), Toast.LENGTH_LONG).show(); Toast.makeText(context, "Error loading properties: " + e.getMessage(), Toast.LENGTH_LONG).show();
Log.e("termux", "Error loading props", e); Log.e("termux", "Error loading props", e);

View File

@@ -16,8 +16,6 @@ package com.termux.drawer;
* limitations under the License. * limitations under the License.
*/ */
import java.util.List;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
@@ -27,8 +25,6 @@ import android.graphics.PixelFormat;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.Gravity; import android.view.Gravity;
@@ -39,6 +35,8 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo;
import java.util.List;
/** /**
* DrawerLayout acts as a top-level container for window content that allows for interactive "drawer" views to be pulled * DrawerLayout acts as a top-level container for window content that allows for interactive "drawer" views to be pulled
* out from the edge of the window. * out from the edge of the window.
@@ -1422,38 +1420,6 @@ public class DrawerLayout extends ViewGroup {
return super.onKeyUp(keyCode, event); return super.onKeyUp(keyCode, event);
} }
@Override
protected void onRestoreInstanceState(Parcelable state) {
final SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.openDrawerGravity != Gravity.NO_GRAVITY) {
final View toOpen = findDrawerWithGravity(ss.openDrawerGravity);
if (toOpen != null) {
openDrawer(toOpen);
}
}
setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT);
setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT);
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
final SavedState ss = new SavedState(superState);
final View openDrawer = findOpenDrawer();
if (openDrawer != null) {
ss.openDrawerGravity = ((LayoutParams) openDrawer.getLayoutParams()).gravity;
}
ss.lockModeLeft = mLockModeLeft;
ss.lockModeRight = mLockModeRight;
return ss;
}
@Override @Override
public void addView(View child, int index, ViewGroup.LayoutParams params) { public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params); super.addView(child, index, params);
@@ -1486,30 +1452,6 @@ public class DrawerLayout extends ViewGroup {
&& child.getImportantForAccessibility() != View.IMPORTANT_FOR_ACCESSIBILITY_NO; && child.getImportantForAccessibility() != View.IMPORTANT_FOR_ACCESSIBILITY_NO;
} }
/**
* State persisted across instances
*/
protected static class SavedState extends BaseSavedState {
int openDrawerGravity = Gravity.NO_GRAVITY;
int lockModeLeft = LOCK_MODE_UNLOCKED;
int lockModeRight = LOCK_MODE_UNLOCKED;
public SavedState(Parcel in) {
super(in);
openDrawerGravity = in.readInt();
}
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(openDrawerGravity);
}
}
private class ViewDragCallback extends ViewDragHelper.Callback { private class ViewDragCallback extends ViewDragHelper.Callback {
private final int mAbsGravity; private final int mAbsGravity;
private ViewDragHelper mDragger; private ViewDragHelper mDragger;

View File

@@ -61,6 +61,10 @@ public final class TerminalBuffer {
TerminalRow lineObject = mLines[externalToInternalRow(row)]; TerminalRow lineObject = mLines[externalToInternalRow(row)];
int x1Index = lineObject.findStartOfColumn(x1); int x1Index = lineObject.findStartOfColumn(x1);
int x2Index = (x2 < mColumns) ? lineObject.findStartOfColumn(x2) : lineObject.getSpaceUsed(); int x2Index = (x2 < mColumns) ? lineObject.findStartOfColumn(x2) : lineObject.getSpaceUsed();
if (x2Index == x1Index) {
// Selected the start of a wide character.
x2Index = lineObject.findStartOfColumn(x2+1);
}
char[] line = lineObject.mText; char[] line = lineObject.mText;
int lastPrintingCharIndex = -1; int lastPrintingCharIndex = -1;
int i; int i;
@@ -71,7 +75,7 @@ public final class TerminalBuffer {
} else { } else {
for (i = x1Index; i < x2Index; ++i) { for (i = x1Index; i < x2Index; ++i) {
char c = line[i]; char c = line[i];
if (c != ' ' && !Character.isLowSurrogate(c)) lastPrintingCharIndex = i; if (c != ' ') lastPrintingCharIndex = i;
} }
} }
if (lastPrintingCharIndex != -1) builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1); if (lastPrintingCharIndex != -1) builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);

View File

@@ -184,6 +184,7 @@ public final class TerminalRow {
mSpaceUsed += javaCharDifference; mSpaceUsed += javaCharDifference;
// Store char. A combining character is stored at the end of the existing contents so that it modifies them: // Store char. A combining character is stored at the end of the existing contents so that it modifies them:
//noinspection ResultOfMethodCallIgnored - since we already now how many java chars is used.
Character.toChars(codePoint, text, oldStartOfColumnIndex + (newIsCombining ? oldCharactersUsedForColumn : 0)); Character.toChars(codePoint, text, oldStartOfColumnIndex + (newIsCombining ? oldCharactersUsedForColumn : 0));
if (oldCodePointDisplayWidth == 2 && newCodePointDisplayWidth == 1) { if (oldCodePointDisplayWidth == 2 && newCodePointDisplayWidth == 1) {

View File

@@ -14,11 +14,11 @@ public interface TerminalKeyListener {
/** Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}. */ /** Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}. */
float onScale(float scale); float onScale(float scale);
void onLongPress(MotionEvent e);
/** On a single tap on the terminal if terminal mouse reporting not enabled. */ /** On a single tap on the terminal if terminal mouse reporting not enabled. */
void onSingleTapUp(MotionEvent e); void onSingleTapUp(MotionEvent e);
boolean shouldBackButtonBeMappedToEscape(); boolean shouldBackButtonBeMappedToEscape();
void copyModeChanged(boolean copyMode);
} }

View File

@@ -1,28 +1,27 @@
package com.termux.view; package com.termux.view;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;
import com.termux.terminal.EmulatorDebug;
import com.termux.terminal.KeyHandler;
import com.termux.terminal.TerminalColors;
import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.ActionMode;
import android.view.HapticFeedbackConstants; import android.view.HapticFeedbackConstants;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.KeyCharacterMap; import android.view.KeyCharacterMap;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.BaseInputConnection;
@@ -30,11 +29,24 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import android.widget.Scroller; import android.widget.Scroller;
import com.termux.R;
import com.termux.terminal.EmulatorDebug;
import com.termux.terminal.KeyHandler;
import com.termux.terminal.TerminalBuffer;
import com.termux.terminal.TerminalColors;
import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;
/** View displaying and interacting with a {@link TerminalSession}. */ /** View displaying and interacting with a {@link TerminalSession}. */
public final class TerminalView extends View { public final class TerminalView extends View {
/** Log view key and IME events. */ /** Log view key and IME events. */
private static final boolean LOG_KEY_EVENTS = false; private static final boolean LOG_KEY_EVENTS = true;
/** The currently displayed terminal session, whose emulator is {@link #mEmulator}. */ /** The currently displayed terminal session, whose emulator is {@link #mEmulator}. */
TerminalSession mTermSession; TerminalSession mTermSession;
@@ -51,9 +63,11 @@ public final class TerminalView extends View {
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */ /** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
boolean mVirtualControlKeyDown, mVirtualFnKeyDown; boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
boolean mIsSelectingText = false; boolean mIsSelectingText = false, mIsDraggingLeftSelection, mInitialTextSelection;
int mSelXAnchor = -1, mSelYAnchor = -1;
int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1; int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1;
float mSelectionDownX, mSelectionDownY;
private ActionMode mActionMode;
private BitmapDrawable mLeftSelectionHandle, mRightSelectionHandle;
float mScaleFactor = 1.f; float mScaleFactor = 1.f;
final GestureAndScaleRecognizer mGestureRecognizer; final GestureAndScaleRecognizer mGestureRecognizer;
@@ -78,7 +92,7 @@ public final class TerminalView extends View {
@Override @Override
public boolean onUp(MotionEvent e) { public boolean onUp(MotionEvent e) {
mScrollRemainder = 0.0f; mScrollRemainder = 0.0f;
if (mEmulator != null && mEmulator.isMouseTrackingActive()) { if (mEmulator != null && mEmulator.isMouseTrackingActive() && !mIsSelectingText) {
// Quick event processing when mouse tracking is active - do not wait for check of double tapping // Quick event processing when mouse tracking is active - do not wait for check of double tapping
// for zooming. // for zooming.
sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, true); sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, true);
@@ -91,6 +105,7 @@ public final class TerminalView extends View {
@Override @Override
public boolean onSingleTapUp(MotionEvent e) { public boolean onSingleTapUp(MotionEvent e) {
if (mEmulator == null) return true; if (mEmulator == null) return true;
if (mIsSelectingText) { toggleSelectingText(null); return true; }
requestFocus(); requestFocus();
if (!mEmulator.isMouseTrackingActive()) { if (!mEmulator.isMouseTrackingActive()) {
if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) { if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) {
@@ -103,7 +118,7 @@ public final class TerminalView extends View {
@Override @Override
public boolean onScroll(MotionEvent e2, float distanceX, float distanceY) { public boolean onScroll(MotionEvent e2, float distanceX, float distanceY) {
if (mEmulator == null) return true; if (mEmulator == null || mIsSelectingText) return true;
if (mEmulator.isMouseTrackingActive() && e2.isFromSource(InputDevice.SOURCE_MOUSE)) { if (mEmulator.isMouseTrackingActive() && e2.isFromSource(InputDevice.SOURCE_MOUSE)) {
// If moving with mouse pointer while pressing button, report that instead of scroll. // If moving with mouse pointer while pressing button, report that instead of scroll.
// This means that we never report moving with button press-events for touch input, // This means that we never report moving with button press-events for touch input,
@@ -121,6 +136,7 @@ public final class TerminalView extends View {
@Override @Override
public boolean onScale(float focusX, float focusY, float scale) { public boolean onScale(float focusX, float focusY, float scale) {
if (mEmulator == null || mIsSelectingText) return true;
mScaleFactor *= scale; mScaleFactor *= scale;
mScaleFactor = mOnKeyListener.onScale(mScaleFactor); mScaleFactor = mOnKeyListener.onScale(mScaleFactor);
return true; return true;
@@ -128,7 +144,7 @@ public final class TerminalView extends View {
@Override @Override
public boolean onFling(final MotionEvent e2, float velocityX, float velocityY) { public boolean onFling(final MotionEvent e2, float velocityX, float velocityY) {
if (mEmulator == null) return true; if (mEmulator == null || mIsSelectingText) return true;
// Do not start scrolling until last fling has been taken care of: // Do not start scrolling until last fling has been taken care of:
if (!mScroller.isFinished()) return true; if (!mScroller.isFinished()) return true;
@@ -175,9 +191,9 @@ public final class TerminalView extends View {
@Override @Override
public void onLongPress(MotionEvent e) { public void onLongPress(MotionEvent e) {
if (mEmulator != null && !mGestureRecognizer.isInProgress()) { if (!mGestureRecognizer.isInProgress() && !mIsSelectingText) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
mOnKeyListener.onLongPress(e); toggleSelectingText(e);
} }
} }
}); });
@@ -228,7 +244,7 @@ public final class TerminalView extends View {
// //
// So a bit messy. If this gets too messy it's perhaps best resolved by reverting back to just // So a bit messy. If this gets too messy it's perhaps best resolved by reverting back to just
// "TYPE_NULL" and let the Pinyin Input english keyboard be in word mode. // "TYPE_NULL" and let the Pinyin Input english keyboard be in word mode.
outAttrs.inputType = InputType.TYPE_NULL | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; outAttrs.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
// Let part of the application show behind when in landscape: // Let part of the application show behind when in landscape:
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN; outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN;
@@ -335,20 +351,36 @@ public final class TerminalView extends View {
public void onScreenUpdated() { public void onScreenUpdated() {
if (mEmulator == null) return; if (mEmulator == null) return;
boolean skipScrolling = false;
if (mIsSelectingText) { if (mIsSelectingText) {
// Do not scroll when selecting text.
int rowsInHistory = mEmulator.getScreen().getActiveTranscriptRows();
int rowShift = mEmulator.getScrollCounter(); int rowShift = mEmulator.getScrollCounter();
mSelY1 -= rowShift; if (-mTopRow + rowShift > rowsInHistory) {
mSelY2 -= rowShift; // .. unless we're hitting the end of history transcript, in which
mSelYAnchor -= rowShift; // case we abort text selection and scroll to end.
toggleSelectingText(null);
} else {
skipScrolling = true;
mTopRow -= rowShift;
mSelY1 -= rowShift;
mSelY2 -= rowShift;
}
} }
mEmulator.clearScrollCounter();
if (mTopRow != 0) { if (!skipScrolling && mTopRow != 0) {
// Scroll down if not already there. // Scroll down if not already there.
if (mTopRow < -3) {
// Awaken scroll bars only if scrolling a noticeable amount
// - we do not want visible scroll bars during normal typing
// of one row at a time.
awakenScrollBars();
}
mTopRow = 0; mTopRow = 0;
scrollTo(0, 0);
} }
mEmulator.clearScrollCounter();
invalidate(); invalidate();
} }
@@ -422,82 +454,105 @@ public final class TerminalView extends View {
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@Override @Override
@TargetApi(23)
public boolean onTouchEvent(MotionEvent ev) { public boolean onTouchEvent(MotionEvent ev) {
if (mEmulator == null) return true; if (mEmulator == null) return true;
final boolean eventFromMouse = ev.isFromSource(InputDevice.SOURCE_MOUSE);
final int action = ev.getAction(); final int action = ev.getAction();
if (eventFromMouse) {
if ((ev.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
if (action == MotionEvent.ACTION_DOWN) showContextMenu();
return true;
} else if (mEmulator.isMouseTrackingActive() && (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_UP)) {
sendMouseEventCode(ev, TerminalEmulator.MOUSE_LEFT_BUTTON, ev.getAction() == MotionEvent.ACTION_DOWN);
return true;
} else if (!mEmulator.isMouseTrackingActive() && action == MotionEvent.ACTION_DOWN) {
// Start text selection with mouse. Note that the check against MotionEvent.ACTION_DOWN is
// important, since we otherwise would pick up secondary mouse button up actions.
mIsSelectingText = true;
}
} else if (!mIsSelectingText) {
mGestureRecognizer.onTouchEvent(ev);
return true;
}
if (mIsSelectingText) { if (mIsSelectingText) {
int cy = (int) (ev.getY() / mRenderer.mFontLineSpacing) + mTopRow;
int cx = (int) (ev.getX() / mRenderer.mFontWidth); int cx = (int) (ev.getX() / mRenderer.mFontWidth);
// Offset for finger:
final int SELECT_TEXT_OFFSET_Y = eventFromMouse ? 0 : -40;
int cy = (int) ((ev.getY() + SELECT_TEXT_OFFSET_Y) / mRenderer.mFontLineSpacing) + mTopRow;
switch (action) { switch (action) {
case MotionEvent.ACTION_UP:
mInitialTextSelection = false;
break;
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
mSelXAnchor = cx; int distanceFromSel1 = Math.abs(cx-mSelX1) + Math.abs(cy-mSelY1);
mSelYAnchor = cy; int distanceFromSel2 = Math.abs(cx-mSelX2) + Math.abs(cy-mSelY2);
mSelX1 = cx; mIsDraggingLeftSelection = distanceFromSel1 <= distanceFromSel2;
mSelY1 = cy; mSelectionDownX = ev.getX();
mSelX2 = mSelX1; mSelectionDownY = ev.getY();
mSelY2 = mSelY1;
invalidate();
break; break;
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP: if (mInitialTextSelection) break;
boolean touchBeforeAnchor = (cy < mSelYAnchor || (cy == mSelYAnchor && cx < mSelXAnchor)); float deltaX = ev.getX() - mSelectionDownX;
int minx = touchBeforeAnchor ? cx : mSelXAnchor; float deltaY = ev.getY() - mSelectionDownY;
int maxx = !touchBeforeAnchor ? cx : mSelXAnchor; int deltaCols = (int) Math.ceil(deltaX / mRenderer.mFontWidth);
int miny = touchBeforeAnchor ? cy : mSelYAnchor; int deltaRows = (int) Math.ceil(deltaY / mRenderer.mFontLineSpacing);
int maxy = !touchBeforeAnchor ? cy : mSelYAnchor; mSelectionDownX += deltaCols * mRenderer.mFontWidth;
mSelX1 = minx; mSelectionDownY += deltaRows * mRenderer.mFontLineSpacing;
mSelY1 = miny; if (mIsDraggingLeftSelection) {
mSelX2 = maxx; mSelX1 += deltaCols;
mSelY2 = maxy; mSelY1 += deltaRows;
if (action == MotionEvent.ACTION_UP) { } else {
String selectedText = mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2).trim(); mSelX2 += deltaCols;
mTermSession.clipboardText(selectedText); mSelY2 += deltaRows;
toggleSelectingText();
} }
mSelX1 = Math.min(mEmulator.mColumns, Math.max(0, mSelX1));
mSelX2 = Math.min(mEmulator.mColumns, Math.max(0, mSelX2));
if (mSelY1 == mSelY2 && mSelX1 > mSelX2 || mSelY1 > mSelY2) {
// Switch handles.
mIsDraggingLeftSelection = !mIsDraggingLeftSelection;
int tmpX1 = mSelX1, tmpY1 = mSelY1;
mSelX1 = mSelX2; mSelY1 = mSelY2;
mSelX2 = tmpX1; mSelY2 = tmpY1;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) mActionMode.invalidateContentRect();
invalidate(); invalidate();
break; break;
default: default:
toggleSelectingText();
invalidate();
break; break;
} }
mGestureRecognizer.onTouchEvent(ev);
return true; return true;
} else if (ev.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (ev.isButtonPressed(MotionEvent.BUTTON_SECONDARY)) {
if (action == MotionEvent.ACTION_DOWN) showContextMenu();
return true;
} else if (ev.isButtonPressed(MotionEvent.BUTTON_TERTIARY)) {
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = clipboard.getPrimaryClip();
if (clipData != null) {
CharSequence paste = clipData.getItemAt(0).coerceToText(getContext());
if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString());
}
} else if (mEmulator.isMouseTrackingActive()) { // BUTTON_PRIMARY.
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
sendMouseEventCode(ev, TerminalEmulator.MOUSE_LEFT_BUTTON, ev.getAction() == MotionEvent.ACTION_DOWN);
break;
case MotionEvent.ACTION_MOVE:
sendMouseEventCode(ev, TerminalEmulator.MOUSE_LEFT_BUTTON_MOVED, true);
break;
}
return true;
}
} }
return false; mGestureRecognizer.onTouchEvent(ev);
return true;
} }
@Override @Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) { public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")"); if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
if (keyCode == KeyEvent.KEYCODE_BACK && mOnKeyListener.shouldBackButtonBeMappedToEscape()) { if (keyCode == KeyEvent.KEYCODE_BACK) {
// Intercept back button to treat it as escape: if (mIsSelectingText) {
switch (event.getAction()) { toggleSelectingText(null);
case KeyEvent.ACTION_DOWN: return true;
return onKeyDown(keyCode, event); } else if (mOnKeyListener.shouldBackButtonBeMappedToEscape()) {
case KeyEvent.ACTION_UP: // Intercept back button to treat it as escape:
return onKeyUp(keyCode, event); switch (event.getAction()) {
case KeyEvent.ACTION_DOWN:
return onKeyDown(keyCode, event);
case KeyEvent.ACTION_UP:
return onKeyUp(keyCode, event);
}
} }
} }
return super.onKeyPreIme(keyCode, event); return super.onKeyPreIme(keyCode, event);
@@ -644,13 +699,20 @@ public final class TerminalView extends View {
if (resultingKeyCode > -1) { if (resultingKeyCode > -1) {
handleKeyCode(resultingKeyCode, 0); handleKeyCode(resultingKeyCode, 0);
} else { } else {
// The below two workarounds are needed on at least Logitech Keyboard k810 on Samsung Galaxy Tab Pro // Work around bluetooth keyboards sending funny unicode characters instead
// (Android 4.4) with the stock Samsung Keyboard. They should be harmless when not used since the need // of the more normal ones from ASCII that terminal programs expect - the
// to input the original characters instead of the new ones using the keyboard should be low. // desire to input the original characters should be low.
// Rewrite U+02DC 'SMALL TILDE' to U+007E 'TILDE' for ~ to work in shells: switch (codePoint) {
if (codePoint == 0x02DC) codePoint = 0x07E; case 0x02DC: // SMALL TILDE.
// Rewrite U+02CB 'MODIFIER LETTER GRAVE ACCENT' to U+0060 'GRAVE ACCENT' for ` (backticks) to work: codePoint = 0x007E; // TILDE (~).
if (codePoint == 0x02CB) codePoint = 0x60; break;
case 0x02CB: // MODIFIER LETTER GRAVE ACCENT.
codePoint = 0x0060; // GRAVE ACCENT (`).
break;
case 0x02C6: // MODIFIER LETTER CIRCUMFLEX ACCENT.
codePoint = 0x005E; // CIRCUMFLEX ACCENT (^).
break;
}
// If left alt, send escape before the code point to make e.g. Alt+B and Alt+F work in readline: // If left alt, send escape before the code point to make e.g. Alt+B and Alt+F work in readline:
mTermSession.writeCodePoint(leftAltDownFromEvent, codePoint); mTermSession.writeCodePoint(leftAltDownFromEvent, codePoint);
@@ -710,67 +772,28 @@ public final class TerminalView extends View {
return false; return false;
} }
public void checkForTypeface() { public void checkForFontAndColors() {
new Thread() { try {
@Override File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
public void run() { File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
try {
File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
final Typeface newTypeface = fontFile.exists() ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
if (newTypeface != mRenderer.mTypeface) {
post(new Runnable() {
@Override
public void run() {
try {
mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface);
updateSize();
invalidate();
} catch (Exception e) {
Log.e(EmulatorDebug.LOG_TAG, "Error loading font", e);
}
}
});
}
} catch (Exception e) {
Log.e(EmulatorDebug.LOG_TAG, "Error loading font", e);
}
}
}.start();
}
public void checkForColors() { final Properties props = new Properties();
new Thread() { if (colorsFile.isFile()) {
@Override try (InputStream in = new FileInputStream(colorsFile)) {
public void run() { props.load(in);
try {
File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
final Properties props = colorsFile.isFile() ? new Properties() : null;
if (props != null) {
try (InputStream in = new FileInputStream(colorsFile)) {
props.load(in);
}
}
post(new Runnable() {
@Override
public void run() {
try {
if (props == null) {
TerminalColors.COLOR_SCHEME.reset();
} else {
TerminalColors.COLOR_SCHEME.updateWith(props);
}
if (mEmulator != null) mEmulator.mColors.reset();
invalidate();
} catch (Exception e) {
Log.e(EmulatorDebug.LOG_TAG, "Setting colors failed: " + e.getMessage());
}
}
});
} catch (Exception e) {
Log.e(EmulatorDebug.LOG_TAG, "Failed colors handling", e);
} }
} }
}.start(); TerminalColors.COLOR_SCHEME.updateWith(props);
if (mEmulator != null) mEmulator.mColors.reset();
final Typeface newTypeface = fontFile.exists() ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface);
updateSize();
invalidate();
} catch (Exception e) {
Log.e(EmulatorDebug.LOG_TAG, "Error in checkForFontAndColors()", e);
}
} }
/** /**
@@ -808,13 +831,151 @@ public final class TerminalView extends View {
canvas.drawColor(0XFF000000); canvas.drawColor(0XFF000000);
} else { } else {
mRenderer.render(mEmulator, canvas, mTopRow, mSelY1, mSelY2, mSelX1, mSelX2); mRenderer.render(mEmulator, canvas, mTopRow, mSelY1, mSelY2, mSelX1, mSelX2);
if (mIsSelectingText) {
final int gripHandleWidth = mLeftSelectionHandle.getIntrinsicWidth();
final int gripHandleMargin = gripHandleWidth / 4; // See the png.
int right = Math.round((mSelX1) * mRenderer.mFontWidth) + gripHandleMargin;
int top = (mSelY1+1 - mTopRow)*mRenderer.mFontLineSpacing + mRenderer.mFontLineSpacingAndAscent;
mLeftSelectionHandle.setBounds(right - gripHandleWidth, top, right, top + mLeftSelectionHandle.getIntrinsicHeight());
mLeftSelectionHandle.draw(canvas);
int left = Math.round((mSelX2+1)*mRenderer.mFontWidth) - gripHandleMargin;
top = (mSelY2+1 - mTopRow) *mRenderer.mFontLineSpacing + mRenderer.mFontLineSpacingAndAscent;
mRightSelectionHandle.setBounds(left, top, left + gripHandleWidth, top + mRightSelectionHandle.getIntrinsicHeight());
mRightSelectionHandle.draw(canvas);
}
} }
} }
/** Toggle text selection mode in the view. */ /** Toggle text selection mode in the view. */
public void toggleSelectingText() { @TargetApi(23)
public void toggleSelectingText(MotionEvent ev) {
mIsSelectingText = !mIsSelectingText; mIsSelectingText = !mIsSelectingText;
if (!mIsSelectingText) mSelX1 = mSelY1 = mSelX2 = mSelY2 = -1; mOnKeyListener.copyModeChanged(mIsSelectingText);
if (mIsSelectingText) {
if (mLeftSelectionHandle == null) {
mLeftSelectionHandle = (BitmapDrawable) getContext().getDrawable(R.drawable.text_select_handle_left_material);
mRightSelectionHandle = (BitmapDrawable) getContext().getDrawable(R.drawable.text_select_handle_right_material);
}
int cx = (int) (ev.getX() / mRenderer.mFontWidth);
final boolean eventFromMouse = ev.isFromSource(InputDevice.SOURCE_MOUSE);
// Offset for finger:
final int SELECT_TEXT_OFFSET_Y = eventFromMouse ? 0 : -40;
int cy = (int) ((ev.getY() + SELECT_TEXT_OFFSET_Y) / mRenderer.mFontLineSpacing) + mTopRow;
mSelX1 = mSelX2 = cx;
mSelY1 = mSelY2 = cy;
TerminalBuffer screen = mEmulator.getScreen();
if (!" ".equals(screen.getSelectedText(mSelX1, mSelY1, mSelX1, mSelY1))) {
// Selecting something other than whitespace. Expand to word.
while (mSelX1 > 0 && !"".equals(screen.getSelectedText(mSelX1-1, mSelY1, mSelX1-1, mSelY1))) {
mSelX1--;
}
while (mSelX2 < mEmulator.mColumns-1 && !"".equals(screen.getSelectedText(mSelX2+1, mSelY1, mSelX2+1, mSelY1))) {
mSelX2++;
}
}
mInitialTextSelection = true;
mIsDraggingLeftSelection = true;
mSelectionDownX = ev.getX();
mSelectionDownY = ev.getY();
final ActionMode.Callback callback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
final int[] ACTION_MODE_ATTRS = { android.R.attr.actionModeCopyDrawable, android.R.attr.actionModePasteDrawable, };
TypedArray styledAttributes = getContext().obtainStyledAttributes(ACTION_MODE_ATTRS);
int show = MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT;
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
menu.add(Menu.NONE, 1, Menu.NONE, R.string.copy_text).setIcon(styledAttributes.getResourceId(0, 0)).setShowAsAction(show);
menu.add(Menu.NONE, 2, Menu.NONE, R.string.paste_text).setIcon(styledAttributes.getResourceId(1, 0)).setEnabled(clipboard.hasPrimaryClip()).setShowAsAction(show);
menu.add(Menu.NONE, 3, Menu.NONE, R.string.text_selection_more);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case 1:
String selectedText = mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2).trim();
mTermSession.clipboardText(selectedText);
break;
case 2:
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = clipboard.getPrimaryClip();
if (clipData != null) {
CharSequence paste = clipData.getItemAt(0).coerceToText(getContext());
if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString());
}
break;
case 3:
showContextMenu();
break;
}
toggleSelectingText(null);
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mActionMode = startActionMode(new ActionMode.Callback2() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return callback.onCreateActionMode(mode, menu);
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return callback.onActionItemClicked(mode, item);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
// Ignore.
}
@Override
public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
int x1 = Math.round(mSelX1 * mRenderer.mFontWidth);
int x2 = Math.round(mSelX2 * mRenderer.mFontWidth);
int y1 = Math.round((mSelY1 - mTopRow) * mRenderer.mFontLineSpacing);
int y2 = Math.round((mSelY2 + 1 - mTopRow) * mRenderer.mFontLineSpacing);
outRect.set(Math.min(x1, x2), y1, Math.max(x1, x2), y2);
}
}, ActionMode.TYPE_FLOATING);
} else {
mActionMode = startActionMode(callback);
}
invalidate();
} else {
mActionMode.finish();
mSelX1 = mSelY1 = mSelX2 = mSelY2 = -1;
invalidate();
}
} }
public TerminalSession getCurrentSession() { public TerminalSession getCurrentSession() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/text_select_handle_left_mtrl_alpha"
android:tint="#2196F3" />

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/text_select_handle_right_mtrl_alpha"
android:tint="#2196F3" />

View File

@@ -3,8 +3,6 @@
<string name="application_name">Termux</string> <string name="application_name">Termux</string>
<string name="shared_user_label">Termux user</string> <string name="shared_user_label">Termux user</string>
<string name="new_session">New session</string> <string name="new_session">New session</string>
<string name="new_session_normal_unnamed">Normal - unnamed</string>
<string name="new_session_normal_named">Normal - named</string>
<string name="new_session_failsafe">Failsafe</string> <string name="new_session_failsafe">Failsafe</string>
<string name="toggle_soft_keyboard">Keyboard</string> <string name="toggle_soft_keyboard">Keyboard</string>
<string name="reset_terminal">Reset</string> <string name="reset_terminal">Reset</string>
@@ -29,18 +27,18 @@
<string name="reset_toast_notification">Terminal reset.</string> <string name="reset_toast_notification">Terminal reset.</string>
<string name="select">Select…</string>
<string name="select_text">Select text</string>
<string name="select_url">Select URL</string> <string name="select_url">Select URL</string>
<string name="select_url_dialog_title">Click URL to copy or long press to open</string> <string name="select_url_dialog_title">Click URL to copy or long press to open</string>
<string name="select_all_and_share">Select all text and share</string> <string name="select_all_and_share">Share transcript</string>
<string name="select_url_no_found">No URL found in the terminal.</string> <string name="select_url_no_found">No URL found in the terminal.</string>
<string name="select_url_copied_to_clipboard">URL copied to clipboard</string> <string name="select_url_copied_to_clipboard">URL copied to clipboard</string>
<string name="share_transcript_chooser_title">Send text to:</string> <string name="share_transcript_chooser_title">Send text to:</string>
<string name="paste_text">Paste</string> <string name="paste_text">Paste</string>
<string name="kill_process">Hangup</string> <string name="copy_text">Copy</string>
<string name="text_selection_more">More…</string>
<string name="kill_process">Hangup</string>
<string name="confirm_kill_process">Close this process?</string> <string name="confirm_kill_process">Close this process?</string>
<string name="session_rename_title">Set session name</string> <string name="session_rename_title">Set session name</string>

View File

@@ -6,10 +6,13 @@
<style name="Theme.Termux" parent="@android:style/Theme.Material.Light.NoActionBar"> <style name="Theme.Termux" parent="@android:style/Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">#000000</item> <item name="android:statusBarColor">#000000</item>
<item name="android:windowBackground">@android:color/black</item> <item name="android:windowBackground">@android:color/black</item>
<!-- Seen in buttons on left drawer: --> <!-- Seen in buttons on left drawer: -->
<item name="android:colorAccent">#212121</item> <item name="android:colorAccent">#212121</item>
<item name="android:alertDialogTheme">@style/TermuxAlertDialogStyle</item> <item name="android:alertDialogTheme">@style/TermuxAlertDialogStyle</item>
<!-- Avoid action mode toolbar pushing down terminal content when
selecting text on pre-6.0 (non-floating toolbar). -->
<item name="android:windowActionModeOverlay">true</item>
</style> </style>
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert"> <style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
@@ -17,4 +20,4 @@
<item name="android:colorAccent">#212121</item> <item name="android:colorAccent">#212121</item>
</style> </style>
</resources> </resources>

View File

@@ -111,6 +111,10 @@ public class KeyHandlerTest extends TestCase {
// Backspace. // Backspace.
assertKeysEquals("\u007f", KeyHandler.getCode(KeyEvent.KEYCODE_DEL, 0, false, false)); assertKeysEquals("\u007f", KeyHandler.getCode(KeyEvent.KEYCODE_DEL, 0, false, false));
// Space.
assertNull(KeyHandler.getCode(KeyEvent.KEYCODE_SPACE, 0, false, false));
assertKeysEquals("\u0000", KeyHandler.getCode(KeyEvent.KEYCODE_SPACE, KeyHandler.KEYMOD_CTRL, false, false));
// Back tab. // Back tab.
assertKeysEquals("\033[Z", KeyHandler.getCode(KeyEvent.KEYCODE_TAB, KeyHandler.KEYMOD_SHIFT, false, false)); assertKeysEquals("\033[Z", KeyHandler.getCode(KeyEvent.KEYCODE_TAB, KeyHandler.KEYMOD_SHIFT, false, false));

90
gradlew.bat vendored
View File

@@ -1,90 +0,0 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

Binary file not shown.