diff --git a/app/build.gradle b/app/build.gradle index 8cda90a..701e04d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,6 +55,7 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" testCompile 'junit:junit:4.12' compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile 'org.greenrobot:eventbus:3.0.0' compile project(':chrome-tabs') implementation 'com.android.support:design:25.3.1' } diff --git a/app/src/main/assets/install-oh-my-zsh.sh b/app/src/main/assets/install-zsh.sh similarity index 100% rename from app/src/main/assets/install-oh-my-zsh.sh rename to app/src/main/assets/install-zsh.sh diff --git a/app/src/main/java/io/neoterm/customize/NeoTermPath.kt b/app/src/main/java/io/neoterm/customize/NeoTermPath.kt index a74bf4d..61f7f4d 100644 --- a/app/src/main/java/io/neoterm/customize/NeoTermPath.kt +++ b/app/src/main/java/io/neoterm/customize/NeoTermPath.kt @@ -8,12 +8,15 @@ import android.annotation.SuppressLint object NeoTermPath { @SuppressLint("SdCardPath") const val ROOT_PATH = "/data/data/io.neoterm/files" - const val USR_PATH = "$ROOT_PATH/usr" + const val USR_PATH = "$ROOT_PATH/usr" const val HOME_PATH = "$ROOT_PATH/home" const val EKS_PATH = "$USR_PATH/share/eks" const val EKS_DEFAULT_FILE = "$EKS_PATH/default.eks" - const val SERVER_BASE_URL = "http://mirror.neoterm.studio" + const val SOURCE_FILE = "$USR_PATH/etc/apt/sources.list" + + const val DEFAULT_SOURCE = "http://mirror.neoterm.studio" + const val SERVER_BASE_URL = DEFAULT_SOURCE const val SERVER_BOOT_URL = "$SERVER_BASE_URL/boot" } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/customize/shortcut/ShortcutConfigLoader.kt b/app/src/main/java/io/neoterm/customize/shortcut/ShortcutConfigLoader.kt index 593ca64..8b3eddb 100644 --- a/app/src/main/java/io/neoterm/customize/shortcut/ShortcutConfigLoader.kt +++ b/app/src/main/java/io/neoterm/customize/shortcut/ShortcutConfigLoader.kt @@ -15,7 +15,7 @@ object ShortcutConfigLoader { extraKeysView.loadDefaultUserDefinedExtraKeys() } for (button in config.shortcutKeys) { - extraKeysView.addExternalButton(button) + extraKeysView.addUserDefinedButton(button) } } } diff --git a/app/src/main/java/io/neoterm/installer/BaseFileInstaller.java b/app/src/main/java/io/neoterm/installer/BaseFileInstaller.java index 8d019f1..4e27548 100644 --- a/app/src/main/java/io/neoterm/installer/BaseFileInstaller.java +++ b/app/src/main/java/io/neoterm/installer/BaseFileInstaller.java @@ -2,6 +2,7 @@ package io.neoterm.installer; import android.app.Activity; import android.app.ProgressDialog; +import android.content.Context; import android.os.Build; import android.system.Os; import android.util.Log; @@ -10,7 +11,9 @@ import android.util.Pair; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; +import java.io.InputStream; import java.io.InputStreamReader; +import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; @@ -22,6 +25,7 @@ import java.util.zip.ZipInputStream; import io.neoterm.R; import io.neoterm.backend.EmulatorDebug; import io.neoterm.customize.NeoTermPath; +import io.neoterm.utils.FileUtils; public final class BaseFileInstaller { public interface ResultListener { @@ -35,7 +39,11 @@ public final class BaseFileInstaller { return; } - final ProgressDialog progress = ProgressDialog.show(activity, null, activity.getString(R.string.installer_message), true, false); + installHomeFiles(activity); + + final ProgressDialog progress = makeProgressDialog(activity); + progress.setMax(100); + progress.show(); new Thread() { @Override public void run() { @@ -47,13 +55,37 @@ public final class BaseFileInstaller { deleteFolder(STAGING_PREFIX_FILE); } + int totalBytes = 0; + int totalReadBytes = 0; final byte[] buffer = new byte[8096]; final List> symlinks = new ArrayList<>(50); final URL zipUrl = determineZipUrl(); - try (ZipInputStream zipInput = new ZipInputStream(zipUrl.openStream())) { + HttpURLConnection connection = (HttpURLConnection) zipUrl.openConnection(); + totalBytes = connection.getContentLength(); + + try (ZipInputStream zipInput = new ZipInputStream(connection.getInputStream())) { ZipEntry zipEntry; + while ((zipEntry = zipInput.getNextEntry()) != null) { + totalReadBytes += zipEntry.getCompressedSize(); + + final int totalReadBytesFinal = totalReadBytes; + final int totalBytesFinal = totalBytes; + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + try { + double progressFloat = ((double) totalReadBytesFinal) / ((double) totalBytesFinal) * 100.0; + Log.e("NeoTerm-Installer", "total: " + totalBytesFinal + ", read: " + totalReadBytesFinal + ", " + progressFloat); + progress.setProgress((int) progressFloat); + } catch (RuntimeException ignore) { + // activity dismissed + } + } + }); + if (zipEntry.getName().equals("SYMLINKS.txt")) { BufferedReader symlinksReader = new BufferedReader(new InputStreamReader(zipInput)); String line; @@ -77,8 +109,9 @@ public final class BaseFileInstaller { } else { try (FileOutputStream outStream = new FileOutputStream(targetFile)) { int readBytes; - while ((readBytes = zipInput.read(buffer)) != -1) + 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 @@ -89,6 +122,8 @@ public final class BaseFileInstaller { } } + connection.disconnect(); + if (symlinks.isEmpty()) throw new RuntimeException("No SYMLINKS.txt encountered"); for (Pair symlink : symlinks) { @@ -133,6 +168,26 @@ public final class BaseFileInstaller { }.start(); } + private static void installHomeFiles(final Activity activity) { + File HOME_PATH = new File(NeoTermPath.HOME_PATH); + File ZSH_INSTALLER = new File(HOME_PATH, "install-zsh.sh"); + + if (!HOME_PATH.exists()) { + HOME_PATH.mkdirs(); + } + + if (!ZSH_INSTALLER.exists()) { + try { + InputStream inputStream = activity.getAssets().open("install-zsh.sh"); + FileUtils.INSTANCE.writeFile(ZSH_INSTALLER, inputStream); + inputStream.close(); + + Os.chmod(ZSH_INSTALLER.getAbsolutePath(), 0700); + } catch (Exception ignore) { + } + } + } + private static URL determineZipUrl() throws MalformedURLException { String archName = determineArchName(); return new URL(NeoTermPath.SERVER_BOOT_URL + "/" + archName + ".zip"); @@ -166,4 +221,13 @@ public final class BaseFileInstaller { throw new RuntimeException("Unable to delete " + (fileOrDirectory.isDirectory() ? "directory " : "file ") + fileOrDirectory.getAbsolutePath()); } } + + public static ProgressDialog makeProgressDialog(Context context) { + ProgressDialog dialog = new ProgressDialog(context); + dialog.setMessage(context.getString(R.string.installer_message)); + dialog.setIndeterminate(false); + dialog.setCancelable(false); + dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + return dialog; + } } diff --git a/app/src/main/java/io/neoterm/preference/NeoTermPreference.kt b/app/src/main/java/io/neoterm/preference/NeoPreference.kt similarity index 87% rename from app/src/main/java/io/neoterm/preference/NeoTermPreference.kt rename to app/src/main/java/io/neoterm/preference/NeoPreference.kt index 3f693b8..a0234e1 100644 --- a/app/src/main/java/io/neoterm/preference/NeoTermPreference.kt +++ b/app/src/main/java/io/neoterm/preference/NeoPreference.kt @@ -3,9 +3,11 @@ package io.neoterm.preference import android.content.Context import android.content.SharedPreferences import android.preference.PreferenceManager +import io.neoterm.R import io.neoterm.backend.TerminalSession import io.neoterm.customize.NeoTermPath import io.neoterm.services.NeoTermService +import io.neoterm.utils.FileUtils import java.io.File @@ -13,7 +15,7 @@ import java.io.File * @author kiva */ -object NeoTermPreference { +object NeoPreference { const val KEY_FONT_SIZE = "neoterm_general_font_size" const val KEY_CURRENT_SESSION = "neoterm_service_current_session" @@ -23,6 +25,17 @@ object NeoTermPreference { fun init(context: Context) { this.context = context preference = PreferenceManager.getDefaultSharedPreferences(context) + + // load apt source + val sourceFile = File(NeoTermPath.SOURCE_FILE) + val bytes = FileUtils.readFile(sourceFile) + if (bytes != null) { + val source = String(FileUtils.readFile(sourceFile)!!).trim().trimEnd() + val array = source.split(" ") + if (array.size >= 2 && array[0] == "deb") { + store(R.string.key_package_source, array[1]) + } + } } fun cleanup() { @@ -68,7 +81,7 @@ object NeoTermPreference { fun storeCurrentSession(session: TerminalSession) { preference!!.edit() - .putString(NeoTermPreference.KEY_CURRENT_SESSION, session.mHandle) + .putString(NeoPreference.KEY_CURRENT_SESSION, session.mHandle) .apply() } diff --git a/app/src/main/java/io/neoterm/services/NeoTermService.kt b/app/src/main/java/io/neoterm/services/NeoTermService.kt index b4f3d9d..3e68921 100644 --- a/app/src/main/java/io/neoterm/services/NeoTermService.kt +++ b/app/src/main/java/io/neoterm/services/NeoTermService.kt @@ -15,7 +15,7 @@ import io.neoterm.R import io.neoterm.backend.EmulatorDebug import io.neoterm.backend.TerminalSession import io.neoterm.customize.NeoTermPath -import io.neoterm.preference.NeoTermPreference +import io.neoterm.preference.NeoPreference import io.neoterm.ui.NeoTermActivity import java.io.File import java.util.* @@ -83,7 +83,7 @@ class NeoTermService : Service() { executablePath = if (systemShell) "/system/bin/sh" else - NeoTermPath.USR_PATH + "/bin/" + NeoTermPreference.loadString(R.string.key_general_shell, "sh") + NeoTermPath.USR_PATH + "/bin/" + NeoPreference.loadString(R.string.key_general_shell, "sh") if (!File(executablePath).exists()) { Toast.makeText(this, getString(R.string.shell_not_found, executablePath), Toast.LENGTH_LONG).show() @@ -96,7 +96,7 @@ class NeoTermService : Service() { } val session = TerminalSession(executablePath, cwd, arguments, - env ?: NeoTermPreference.buildEnvironment(cwd, systemShell, executablePath), + env ?: NeoPreference.buildEnvironment(cwd, systemShell, executablePath), sessionCallback) mTerminalSessions.add(session) updateNotification() diff --git a/app/src/main/java/io/neoterm/ui/NeoTermActivity.kt b/app/src/main/java/io/neoterm/ui/NeoTermActivity.kt index 5a01bd8..66c30b1 100644 --- a/app/src/main/java/io/neoterm/ui/NeoTermActivity.kt +++ b/app/src/main/java/io/neoterm/ui/NeoTermActivity.kt @@ -16,7 +16,6 @@ import android.view.View import android.view.WindowManager import android.view.inputmethod.InputMethodManager import android.widget.ImageButton -import android.widget.Toast import de.mrapp.android.tabswitcher.* import io.neoterm.R import io.neoterm.backend.TerminalSession @@ -25,15 +24,20 @@ import io.neoterm.customize.shortcut.ShortcutConfigLoader import io.neoterm.customize.shortcut.builtin.BuiltinShortcutKeys import io.neoterm.installer.BaseFileInstaller import io.neoterm.preference.NeoPermission -import io.neoterm.preference.NeoTermPreference +import io.neoterm.preference.NeoPreference import io.neoterm.services.NeoTermService import io.neoterm.ui.settings.SettingActivity -import io.neoterm.utils.NeoTermFullScreen +import io.neoterm.utils.FullScreenHelper +import io.neoterm.view.eks.StatedControlButton import io.neoterm.view.tab.* +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreferences.OnSharedPreferenceChangeListener { lateinit var tabSwitcher: TabSwitcher + lateinit var fullScreenToggleButton: StatedControlButton var systemShell = true var termService: NeoTermService? = null var restartRequired = false @@ -43,16 +47,17 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference NeoPermission.initAppPermission(this, NeoPermission.REQUEST_APP_PERMISSION) FontManager.init(this) - NeoTermPreference.init(this) + NeoPreference.init(this) - if (NeoTermPreference.loadBoolean(R.string.key_ui_fullscreen, false)) { + val fullscreen = NeoPreference.loadBoolean(R.string.key_ui_fullscreen, false) + + if (fullscreen) { window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) } setContentView(R.layout.tab_main) - - NeoTermFullScreen.injectActivity(this) + FullScreenHelper.injectActivity(this, peekRecreating()) .setKeyBoardListener({ isShow, _ -> var tab: TermTab? = null @@ -62,12 +67,24 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference tab?.viewClient?.extraKeysView?.visibility = if (isShow) View.VISIBLE else View.GONE - if (NeoTermPreference.loadBoolean(R.string.key_ui_fullscreen, false) - || NeoTermPreference.loadBoolean(R.string.key_ui_hide_toolbar, false)) { + if (NeoPreference.loadBoolean(R.string.key_ui_fullscreen, false) + || NeoPreference.loadBoolean(R.string.key_ui_hide_toolbar, false)) { tab?.toolbar?.visibility = if (isShow) View.GONE else View.VISIBLE } }) + fullScreenToggleButton = object : StatedControlButton("FS", fullscreen) { + override fun onClick(view: View?) { + super.onClick(view) + if (tabSwitcher.selectedTab is TermTab) { + val tab = tabSwitcher.selectedTab as TermTab + tab.hideIme() + } + NeoPreference.store(R.string.key_ui_fullscreen, super.toggleButton.isChecked) + this@NeoTermActivity.recreate() + } + } + tabSwitcher = findViewById(R.id.tab_switcher) as TabSwitcher tabSwitcher.decorator = TermTabDecorator(this) ViewCompat.setOnApplyWindowInsetsListener(tabSwitcher, createWindowInsetsListener()) @@ -92,9 +109,8 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference super.onResume() if (restartRequired) { restartRequired = false - this.recreate() + recreate() } - PreferenceManager.getDefaultSharedPreferences(this) .registerOnSharedPreferenceChangeListener(this) tabSwitcher.addListener(object : TabSwitcherListener { @@ -123,7 +139,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference override fun onSelectionChanged(tabSwitcher: TabSwitcher, selectedTabIndex: Int, selectedTab: Tab?) { if (selectedTab is TermTab && selectedTab.termSession != null) { - NeoTermPreference.storeCurrentSession(selectedTab.termSession!!) + NeoPreference.storeCurrentSession(selectedTab.termSession!!) } } @@ -143,6 +159,16 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference }) } + override fun onStart() { + super.onStart() + EventBus.getDefault().register(this) + } + + override fun onStop() { + super.onStop() + EventBus.getDefault().unregister(this) + } + override fun onDestroy() { super.onDestroy() PreferenceManager.getDefaultSharedPreferences(this) @@ -154,7 +180,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference termService = null } unbindService(this) - NeoTermPreference.cleanup() + NeoPreference.cleanup() } override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { @@ -185,6 +211,12 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference } } + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + if (key == getString(R.string.key_ui_fullscreen)) { + restartRequired = true + } + } + override fun onServiceDisconnected(name: ComponentName?) { if (termService != null) { finish() @@ -227,14 +259,35 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference } } - BaseFileInstaller.installBaseFiles(this, resultListener) + if (!isRecreating()) { + BaseFileInstaller.installBaseFiles(this, resultListener) + } } - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - if (key == getString(R.string.key_ui_fullscreen)) { - Toast.makeText(this, R.string.fullscreen_mode_changed, Toast.LENGTH_SHORT).show() - restartRequired = true - } + override fun onSaveInstanceState(outState: Bundle?) { + super.onSaveInstanceState(outState) + outState?.putBoolean("system_shell", systemShell) + } + + override fun onRestoreInstanceState(savedInstanceState: Bundle?) { + super.onRestoreInstanceState(savedInstanceState) + systemShell = savedInstanceState?.getBoolean("system_shell", true) ?: true + } + + override fun recreate() { + NeoPreference.store("recreate", true) + super.recreate() + } + + private fun isRecreating(): Boolean { + val result = peekRecreating() + NeoPreference.store("recreate", !result) + return result + } + + private fun peekRecreating(): Boolean { + val result = NeoPreference.loadBoolean("recreate", false) + return result } private fun addNewSession(session: TerminalSession?) { @@ -299,7 +352,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference } private fun getStoredCurrentSessionOrLast(): TerminalSession? { - val stored = NeoTermPreference.getCurrentSession(termService) + val stored = NeoPreference.getCurrentSession(termService) if (stored != null) return stored val numberOfSessions = termService!!.sessions.size if (numberOfSessions == 0) return null @@ -340,24 +393,6 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference private fun createTab(tabTitle: String?): Tab { val tab = TermTab(tabTitle ?: "NeoTerm") - tab.closeTabProvider = object : CloseTabProvider { - override fun closeTab(tab: Tab) { - tabSwitcher.showSwitcher() - tabSwitcher.removeTab(tab) - - if (tabSwitcher.count > 1) { - var index = tabSwitcher.indexOf(tab) - if (NeoTermPreference.loadBoolean(R.string.key_ui_next_tab_anim, false)) { - // 关闭当前窗口后,向下一个窗口切换 - if (--index < 0) index = tabSwitcher.count - 1 - } else { - // 关闭当前窗口后,向上一个窗口切换 - if (++index >= tabSwitcher.count) index = 0 - } - switchToSession(tabSwitcher.getTab(index)) - } - } - } tab.isCloseable = true tab.parameters = Bundle() tab.setBackgroundColor(ContextCompat.getColor(this, R.color.tab_background_color)) @@ -408,4 +443,24 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference insets } } + + @Suppress("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + fun onTabCloseEvent(tabCloseEvent: TabCloseEvent) { + val tab = tabCloseEvent.termTab + tabSwitcher.showSwitcher() + tabSwitcher.removeTab(tab) + + if (tabSwitcher.count > 1) { + var index = tabSwitcher.indexOf(tab) + if (NeoPreference.loadBoolean(R.string.key_ui_next_tab_anim, false)) { + // 关闭当前窗口后,向下一个窗口切换 + if (--index < 0) index = tabSwitcher.count - 1 + } else { + // 关闭当前窗口后,向上一个窗口切换 + if (++index >= tabSwitcher.count) index = 0 + } + switchToSession(tabSwitcher.getTab(index)) + } + } } diff --git a/app/src/main/java/io/neoterm/ui/settings/PackageSettingsActivity.kt b/app/src/main/java/io/neoterm/ui/settings/PackageSettingsActivity.kt index 87dccee..bf0b989 100644 --- a/app/src/main/java/io/neoterm/ui/settings/PackageSettingsActivity.kt +++ b/app/src/main/java/io/neoterm/ui/settings/PackageSettingsActivity.kt @@ -1,9 +1,14 @@ package io.neoterm.ui.settings import android.os.Bundle +import android.support.v7.app.AlertDialog import android.support.v7.app.AppCompatPreferenceActivity import android.view.MenuItem import io.neoterm.R +import io.neoterm.customize.NeoTermPath +import io.neoterm.preference.NeoPreference +import io.neoterm.utils.FileUtils +import java.io.File /** * @author kiva @@ -15,6 +20,33 @@ class PackageSettingsActivity : AppCompatPreferenceActivity() { supportActionBar.title = getString(R.string.package_settings) supportActionBar.setDisplayHomeAsUpEnabled(true) addPreferencesFromResource(R.xml.settings_package) + + val preference = findPreference(getString(R.string.key_package_source)) + preference.summary = NeoPreference.loadString(R.string.key_package_source, NeoTermPath.DEFAULT_SOURCE) + + preference.setOnPreferenceChangeListener { preference, newValue -> + val newSource = newValue as String + preference.summary = newSource + if (newSource.isNotEmpty()) { + val sourceFile = File(NeoTermPath.SOURCE_FILE) + FileUtils.writeFile(sourceFile, generateSourceFile(newSource).toByteArray()) + + AlertDialog.Builder(this@PackageSettingsActivity) + .setMessage(R.string.source_changed) + .setPositiveButton(android.R.string.yes, null) + .show() + } + return@setOnPreferenceChangeListener true + } + } + + private fun generateSourceFile(source: String): String { + return StringBuilder().append("# Generated by NeoTerm-Preference\n") + .append("deb ") + .append(source) + .append(" stable main") + .append("\n") + .toString() } override fun onBuildHeaders(target: MutableList
?) { diff --git a/app/src/main/java/io/neoterm/ui/settings/UISettingsActivity.kt b/app/src/main/java/io/neoterm/ui/settings/UISettingsActivity.kt index 2d9e02f..da6ab69 100644 --- a/app/src/main/java/io/neoterm/ui/settings/UISettingsActivity.kt +++ b/app/src/main/java/io/neoterm/ui/settings/UISettingsActivity.kt @@ -1,5 +1,6 @@ package io.neoterm.ui.settings +import android.app.AlertDialog import android.os.Bundle import android.support.v7.app.AppCompatPreferenceActivity import android.view.MenuItem @@ -16,7 +17,13 @@ class UISettingsActivity : AppCompatPreferenceActivity() { supportActionBar.setDisplayHomeAsUpEnabled(true) addPreferencesFromResource(R.xml.settings_ui) findPreference(getString(R.string.key_ui_suggestions)) - .setOnPreferenceChangeListener({preference, newValue -> + .setOnPreferenceChangeListener({_, newValue -> + if (newValue as Boolean) { + AlertDialog.Builder(this@UISettingsActivity) + .setMessage(R.string.installer_install_zsh_manually) + .setPositiveButton(android.R.string.yes, null) + .show() + } return@setOnPreferenceChangeListener true }) } diff --git a/app/src/main/java/io/neoterm/utils/FileUtils.kt b/app/src/main/java/io/neoterm/utils/FileUtils.kt index c00e573..1a94d72 100644 --- a/app/src/main/java/io/neoterm/utils/FileUtils.kt +++ b/app/src/main/java/io/neoterm/utils/FileUtils.kt @@ -1,31 +1,37 @@ package io.neoterm.utils import java.io.File +import java.io.FileInputStream import java.io.FileOutputStream -import java.io.OutputStream +import java.io.InputStream /** * @author kiva */ object FileUtils { fun writeFile(path: File, bytes: ByteArray): Boolean { - var output: OutputStream? = null - var success = true - try { - output = FileOutputStream(path) - output.write(bytes) - output.flush() - } catch (e: Exception) { - e.printStackTrace() - success = false - } finally { - if (output != null) { - try { - output.close() - } catch (ignore: Exception) { - } - } + return FileOutputStream(path).use { + it.write(bytes) + it.flush() + true + } + } + + fun writeFile(path: File, inputStream: InputStream): Boolean { + val bytes = ByteArray(inputStream.available()) + inputStream.read(bytes) + return writeFile(path, bytes) + } + + fun readFile(path: File): ByteArray? { + if (!path.canRead()) { + return null + } + + return FileInputStream(path).use { + val bytes = ByteArray(it.available()) + it.read(bytes) + bytes } - return success } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/utils/NeoTermFullScreen.java b/app/src/main/java/io/neoterm/utils/FullScreenHelper.java similarity index 88% rename from app/src/main/java/io/neoterm/utils/NeoTermFullScreen.java rename to app/src/main/java/io/neoterm/utils/FullScreenHelper.java index 647e891..db5c756 100644 --- a/app/src/main/java/io/neoterm/utils/NeoTermFullScreen.java +++ b/app/src/main/java/io/neoterm/utils/FullScreenHelper.java @@ -9,11 +9,10 @@ import android.widget.FrameLayout; /** * Helper class to "adjustResize" Activity when we are in full screen mode and check IME status. * Android Bug 5497: https://code.google.com/p/android/issues/detail?id=5497 - * author @kiva */ -public class NeoTermFullScreen { - public static NeoTermFullScreen injectActivity(Activity activity) { - return new NeoTermFullScreen(activity); +public class FullScreenHelper { + public static FullScreenHelper injectActivity(Activity activity, boolean isRecreating) { + return new FullScreenHelper(activity, isRecreating); } public interface KeyBoardListener { @@ -33,12 +32,14 @@ public class NeoTermFullScreen { private int mOriginHeight; private int mPreHeight; private KeyBoardListener mKeyBoardListener; + private boolean shouldSkipFirstTime; public void setKeyBoardListener(KeyBoardListener mKeyBoardListener) { this.mKeyBoardListener = mKeyBoardListener; } - private NeoTermFullScreen(Activity activity) { + private FullScreenHelper(Activity activity, boolean isRecreating) { + this.shouldSkipFirstTime = isRecreating; FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content); mChildOfContent = content.getChildAt(0); mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @@ -52,10 +53,12 @@ public class NeoTermFullScreen { private void monitorImeStatus() { int currHeight = mChildOfContent.getHeight(); - if (currHeight == 0) { + if (currHeight == 0 && shouldSkipFirstTime) { // First time return; } + + shouldSkipFirstTime = false; boolean hasChange = false; if (mPreHeight == 0) { mPreHeight = currHeight; diff --git a/app/src/main/java/io/neoterm/view/ExtraKeysView.java b/app/src/main/java/io/neoterm/view/ExtraKeysView.java index 3ce6f98..d0cb8cc 100755 --- a/app/src/main/java/io/neoterm/view/ExtraKeysView.java +++ b/app/src/main/java/io/neoterm/view/ExtraKeysView.java @@ -50,7 +50,7 @@ public final class ExtraKeysView extends LinearLayout { "define | false\n"; public static final int NORMAL_TEXT_COLOR = 0xFFFFFFFF; - + public static final int SELECTED_TEXT_COLOR = 0xFF80DEEA; private List builtinExtraKeys; private List userDefinedExtraKeys; @@ -85,7 +85,6 @@ public final class ExtraKeysView extends LinearLayout { return line; } - public boolean readControlButton() { return CTRL.readState(); } @@ -94,11 +93,21 @@ public final class ExtraKeysView extends LinearLayout { return false; } - public void addExternalButton(ExtraButton button) { - userDefinedExtraKeys.add(button); + public void addUserDefinedButton(ExtraButton button) { + addButton(userDefinedExtraKeys, button); } - public void clearExternalButton() { + public void addBuiltinButton(ExtraButton button) { + addButton(builtinExtraKeys, button); + } + + private void addButton(List buttons, ExtraButton button) { + if (!buttons.contains(button)) { + buttons.add(button); + } + } + + public void clearUserDefinedButton() { userDefinedExtraKeys.clear(); } @@ -109,7 +118,7 @@ public final class ExtraKeysView extends LinearLayout { generateDefaultFile(defaultFile); } - clearExternalButton(); + clearUserDefinedButton(); try { ShortcutConfigParser parser = new ShortcutConfigParser(); parser.setInput(defaultFile); @@ -153,6 +162,10 @@ public final class ExtraKeysView extends LinearLayout { StatedControlButton btn = ((StatedControlButton) extraButton); button = btn.toggleButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle); button.setClickable(true); + if (btn.initState) { + btn.toggleButton.setChecked(true); + btn.toggleButton.setTextColor(SELECTED_TEXT_COLOR); + } } else { button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle); } diff --git a/app/src/main/java/io/neoterm/view/eks/StatedControlButton.java b/app/src/main/java/io/neoterm/view/eks/StatedControlButton.java index cdbc411..ab29e0a 100644 --- a/app/src/main/java/io/neoterm/view/eks/StatedControlButton.java +++ b/app/src/main/java/io/neoterm/view/eks/StatedControlButton.java @@ -11,15 +11,25 @@ import io.neoterm.view.ExtraKeysView; public class StatedControlButton extends ControlButton { public ToggleButton toggleButton; + public boolean initState; + + public StatedControlButton(String text, boolean initState) { + super(text); + this.initState = initState; + } public StatedControlButton(String text) { - super(text); + this(text, false); } @Override public void onClick(View view) { - toggleButton.setChecked(toggleButton.isChecked()); - toggleButton.setTextColor(toggleButton.isChecked() ? 0xFF80DEEA : ExtraKeysView.NORMAL_TEXT_COLOR); + setStatus(toggleButton.isChecked()); + } + + public void setStatus(boolean status) { + toggleButton.setChecked(status); + toggleButton.setTextColor(status ? ExtraKeysView.SELECTED_TEXT_COLOR : ExtraKeysView.NORMAL_TEXT_COLOR); } public boolean readState() { diff --git a/app/src/main/java/io/neoterm/view/tab/CloseTabProvider.kt b/app/src/main/java/io/neoterm/view/tab/CloseTabProvider.kt deleted file mode 100644 index 3ec733c..0000000 --- a/app/src/main/java/io/neoterm/view/tab/CloseTabProvider.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.neoterm.view.tab - -import de.mrapp.android.tabswitcher.Tab - -/** - * @author kiva - */ -interface CloseTabProvider { - fun closeTab(tab: Tab) -} \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/view/tab/TabCloseEvent.kt b/app/src/main/java/io/neoterm/view/tab/TabCloseEvent.kt new file mode 100644 index 0000000..2203f0e --- /dev/null +++ b/app/src/main/java/io/neoterm/view/tab/TabCloseEvent.kt @@ -0,0 +1,7 @@ +package io.neoterm.view.tab + +/** + * @author kiva + */ + +class TabCloseEvent(var termTab: TermTab) diff --git a/app/src/main/java/io/neoterm/view/tab/TermSessionChangedCallback.kt b/app/src/main/java/io/neoterm/view/tab/TermSessionChangedCallback.kt index 6287b60..1f946b5 100644 --- a/app/src/main/java/io/neoterm/view/tab/TermSessionChangedCallback.kt +++ b/app/src/main/java/io/neoterm/view/tab/TermSessionChangedCallback.kt @@ -7,8 +7,7 @@ import android.media.SoundPool import android.os.Vibrator import io.neoterm.R import io.neoterm.backend.TerminalSession -import io.neoterm.preference.NeoTermPreference -import io.neoterm.view.ExtraKeysView +import io.neoterm.preference.NeoPreference import io.neoterm.view.TerminalView /** @@ -47,7 +46,7 @@ class TermSessionChangedCallback : TerminalSession.SessionChangedCallback { return } - if (NeoTermPreference.loadBoolean(R.string.key_general_bell, false)) { + if (NeoPreference.loadBoolean(R.string.key_general_bell, false)) { if (soundPool == null) { soundPool = SoundPool.Builder().setMaxStreams(1).build() bellId = soundPool!!.load(termView!!.context, R.raw.bell, 1) @@ -55,7 +54,7 @@ class TermSessionChangedCallback : TerminalSession.SessionChangedCallback { soundPool?.play(bellId, 1f, 1f, 0, 0, 1f) } - if (NeoTermPreference.loadBoolean(R.string.key_general_vibrate, false)) { + if (NeoPreference.loadBoolean(R.string.key_general_vibrate, false)) { val vibrator = termView!!.context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator vibrator.vibrate(100) } diff --git a/app/src/main/java/io/neoterm/view/tab/TermTab.kt b/app/src/main/java/io/neoterm/view/tab/TermTab.kt index 0937020..e60dd30 100644 --- a/app/src/main/java/io/neoterm/view/tab/TermTab.kt +++ b/app/src/main/java/io/neoterm/view/tab/TermTab.kt @@ -1,12 +1,15 @@ package io.neoterm.view.tab +import android.content.Context import android.graphics.Color import android.support.v7.widget.Toolbar +import android.view.inputmethod.InputMethodManager import de.mrapp.android.tabswitcher.Tab import io.neoterm.R import io.neoterm.backend.TerminalSession import io.neoterm.customize.color.NeoTermColorScheme -import io.neoterm.preference.NeoTermPreference +import io.neoterm.preference.NeoPreference +import org.greenrobot.eventbus.EventBus /** * @author kiva @@ -18,8 +21,6 @@ class TermTab(title: CharSequence) : Tab(title) { var viewClient: TermViewClient? = null var toolbar: Toolbar? = null - var closeTabProvider: CloseTabProvider? = null - fun changeColorScheme(colorScheme: NeoTermColorScheme?) { colorScheme?.apply() viewClient?.extraKeysView?.setBackgroundColor(Color.parseColor(colorScheme?.background)) @@ -31,7 +32,6 @@ class TermTab(title: CharSequence) : Tab(title) { viewClient?.extraKeysView = null sessionCallback?.termView = null sessionCallback?.termTab = null - closeTabProvider = null toolbar = null termSession = null } @@ -39,7 +39,7 @@ class TermTab(title: CharSequence) : Tab(title) { fun updateTitle(title: String) { this.title = title toolbar?.title = title - if (NeoTermPreference.loadBoolean(R.string.key_ui_suggestions, true)) { + if (NeoPreference.loadBoolean(R.string.key_ui_suggestions, true)) { viewClient?.updateSuggestions(title) } else { viewClient?.removeSuggestions() @@ -51,6 +51,17 @@ class TermTab(title: CharSequence) : Tab(title) { } fun requiredCloseTab() { - closeTabProvider?.closeTab(this) + hideIme() + EventBus.getDefault().post(TabCloseEvent(this)) + } + + fun hideIme() { + val terminalView = viewClient?.termView + if (terminalView != null) { + val imm = terminalView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + if (imm.isActive) { + imm.hideSoftInputFromWindow(terminalView.windowToken, InputMethodManager.HIDE_NOT_ALWAYS) + } + } } } diff --git a/app/src/main/java/io/neoterm/view/tab/TermTabDecorator.kt b/app/src/main/java/io/neoterm/view/tab/TermTabDecorator.kt index 624cd9a..ed63f16 100644 --- a/app/src/main/java/io/neoterm/view/tab/TermTabDecorator.kt +++ b/app/src/main/java/io/neoterm/view/tab/TermTabDecorator.kt @@ -12,7 +12,7 @@ import de.mrapp.android.tabswitcher.Tab import de.mrapp.android.tabswitcher.TabSwitcher import de.mrapp.android.tabswitcher.TabSwitcherDecorator import io.neoterm.R -import io.neoterm.preference.NeoTermPreference +import io.neoterm.preference.NeoPreference import io.neoterm.ui.NeoTermActivity import io.neoterm.view.ExtraKeysView import io.neoterm.view.TerminalView @@ -24,8 +24,12 @@ class TermTabDecorator(val context: NeoTermActivity) : TabSwitcherDecorator() { override fun onInflateView(inflater: LayoutInflater, parent: ViewGroup?, viewType: Int): View { val view = inflater.inflate(R.layout.term, parent, false) val toolbar = view.findViewById(R.id.terminal_toolbar) as Toolbar - toolbar.inflateMenu(R.menu.tab_switcher) + val extraKeysView = view.findViewById(R.id.extra_keys) as ExtraKeysView + extraKeysView.addBuiltinButton(context.fullScreenToggleButton) + extraKeysView.updateButtons() + + toolbar.inflateMenu(R.menu.tab_switcher) toolbar.setOnMenuItemClickListener(context.createToolbarMenuListener()) val menu = toolbar.menu TabSwitcher.setupWithMenu(context.tabSwitcher, menu, { @@ -64,8 +68,9 @@ class TermTabDecorator(val context: NeoTermActivity) : TabSwitcherDecorator() { if (view == null) { return } - view.textSize = NeoTermPreference.loadInt(NeoTermPreference.KEY_FONT_SIZE, 30) + view.textSize = NeoPreference.loadInt(NeoPreference.KEY_FONT_SIZE, 30) view.setTypeface(Typeface.MONOSPACE) + context.fullScreenToggleButton.setStatus(NeoPreference.loadBoolean(R.string.key_ui_fullscreen, false)) if (tab is TermTab) { val termTab = tab diff --git a/app/src/main/java/io/neoterm/view/tab/TermViewClient.kt b/app/src/main/java/io/neoterm/view/tab/TermViewClient.kt index 4f9e0c7..a2c3bfe 100644 --- a/app/src/main/java/io/neoterm/view/tab/TermViewClient.kt +++ b/app/src/main/java/io/neoterm/view/tab/TermViewClient.kt @@ -8,7 +8,7 @@ import android.view.inputmethod.InputMethodManager import io.neoterm.R import io.neoterm.backend.TerminalSession import io.neoterm.customize.shortcut.ShortcutKeysManager -import io.neoterm.preference.NeoTermPreference +import io.neoterm.preference.NeoPreference import io.neoterm.view.ExtraKeysView import io.neoterm.view.TerminalView import io.neoterm.view.TerminalViewClient @@ -33,7 +33,7 @@ class TermViewClient(val context: Context) : TerminalViewClient { val changedSize = (if (increase) 1 else -1) * 2 val fontSize = termView!!.textSize + changedSize termView!!.textSize = fontSize - NeoTermPreference.store(NeoTermPreference.KEY_FONT_SIZE, fontSize) + NeoPreference.store(NeoPreference.KEY_FONT_SIZE, fontSize) return 1.0f } return scale @@ -45,7 +45,7 @@ class TermViewClient(val context: Context) : TerminalViewClient { } override fun shouldBackButtonBeMappedToEscape(): Boolean { - return NeoTermPreference.loadBoolean(R.string.key_generaL_backspace_map_to_esc, false) + return NeoPreference.loadBoolean(R.string.key_generaL_backspace_map_to_esc, false) } override fun copyModeChanged(copyMode: Boolean) { @@ -118,7 +118,7 @@ class TermViewClient(val context: Context) : TerminalViewClient { } fun removeSuggestions() { - extraKeysView?.clearExternalButton() + extraKeysView?.clearUserDefinedButton() } } \ No newline at end of file diff --git a/app/src/main/res/layout/term.xml b/app/src/main/res/layout/term.xml index c4fad6b..5436f99 100644 --- a/app/src/main/res/layout/term.xml +++ b/app/src/main/res/layout/term.xml @@ -20,7 +20,8 @@ android:layout_height="@dimen/eks_height_two_line" android:layout_alignParentBottom="true" android:background="@color/terminal_background" - android:orientation="horizontal" /> + android:orientation="horizontal" + android:visibility="gone" /> 界面设置 Shell %s 未找到, 请先安装. 正在安装 + 你可以通过执行以下命令来配置 zsh 和快捷提示 ~/install-zsh.sh 全屏模式已改变,请重启 NeoTerm NeoTerm 无法取得必需的权限,正在退出 还有这种操作? 使用系统Shell 重试 + APT 源已更改,你可能需要执行 apt update 来更新 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4bb501c..cc6c92d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ Package Settings Installing + You may install zsh and setup suggestions by executing: ~/install-zsh.sh Bell Bell when receiving \'\\a\' @@ -42,6 +43,7 @@ Error System Shell Retry + APT source changed, you may get it updated by executing: apt update Default diff --git a/app/src/main/res/xml/settings_package.xml b/app/src/main/res/xml/settings_package.xml index cbaa987..3c2ab7d 100644 --- a/app/src/main/res/xml/settings_package.xml +++ b/app/src/main/res/xml/settings_package.xml @@ -2,7 +2,7 @@ \ No newline at end of file