From e8e9162fcf15edd73fbedda0d3b0ca470d312bf7 Mon Sep 17 00:00:00 2001 From: imkiva Date: Sat, 8 May 2021 00:11:00 +0800 Subject: [PATCH] MISC: change all indent to 2 spaces (so compat!) --- CODE_OF_CONDUCT.md | 32 +- CONTRIBUTING.md | 1 + NeoLang/build.gradle | 32 +- .../main/java/io/neolang/ast/NeoLangToken.kt | 8 +- .../java/io/neolang/ast/NeoLangTokenType.kt | 22 +- .../java/io/neolang/ast/NeoLangTokenValue.kt | 54 +- .../java/io/neolang/ast/base/NeoLangAst.kt | 6 +- .../io/neolang/ast/node/NeoLangArrayNode.kt | 6 +- .../neolang/ast/node/NeoLangAstBasedNode.kt | 6 +- .../neolang/ast/node/NeoLangAttributeNode.kt | 6 +- .../io/neolang/ast/node/NeoLangBlockNode.kt | 8 +- .../io/neolang/ast/node/NeoLangGroupNode.kt | 14 +- .../io/neolang/ast/node/NeoLangProgramNode.kt | 14 +- .../neolang/ast/node/NeoLangTokenBasedNode.kt | 12 +- .../java/io/neolang/ast/visitor/AstVisitor.kt | 14 +- .../io/neolang/ast/visitor/AstVisitorImpl.kt | 142 +- .../neolang/ast/visitor/IVisitorCallback.kt | 10 +- .../ast/visitor/IVisitorCallbackAdapter.kt | 22 +- .../io/neolang/ast/visitor/VisitorFactory.kt | 12 +- NeoLang/src/main/java/io/neolang/main/Main.kt | 54 +- .../java/io/neolang/parser/NeoLangLexer.kt | 454 +- .../java/io/neolang/parser/NeoLangParser.kt | 330 +- .../neolang/runtime/context/NeoLangContext.kt | 48 +- .../io/neolang/runtime/type/NeoLangArray.kt | 89 +- .../runtime/type/NeoLangArrayElement.kt | 18 +- .../io/neolang/runtime/type/NeoLangValue.kt | 36 +- .../java/io/neolang/visitor/ConfigVisitor.kt | 100 +- .../neolang/visitor/DisplayProcessVisitor.kt | 48 +- .../src/test/java/io/neolang/NeoLangTest.kt | 8 +- NeoTermBridge/build.gradle | 36 +- NeoTermBridge/src/main/AndroidManifest.xml | 4 +- .../main/java/io/neoterm/bridge/Bridge.java | 92 +- .../java/io/neoterm/bridge/SessionId.java | 66 +- NeoTermBridge/src/main/res/values/strings.xml | 2 +- .../io/neoterm/bridge/ExampleUnitTest.java | 10 +- README.md | 6 +- Xorg/build.gradle | 44 +- Xorg/src/main/AndroidManifest.xml | 4 +- .../main/java/io/neoterm/Accelerometer.java | 487 +- Xorg/src/main/java/io/neoterm/Audio.java | 488 +- Xorg/src/main/java/io/neoterm/Clipboard.java | 189 +- .../java/io/neoterm/GLSurfaceView_SDL.java | 2152 +++--- Xorg/src/main/java/io/neoterm/Globals.java | 234 +- Xorg/src/main/java/io/neoterm/Keycodes.java | 1128 ++- .../main/java/io/neoterm/MainActivity.java | 1690 ++-- .../io/neoterm/NeoAccelerometerReader.java | 12 +- .../main/java/io/neoterm/NeoAudioThread.java | 6 +- Xorg/src/main/java/io/neoterm/NeoGLView.java | 18 +- .../src/main/java/io/neoterm/NeoRenderer.java | 12 +- .../main/java/io/neoterm/NeoTextInput.java | 14 +- .../main/java/io/neoterm/NeoXorgSettings.java | 6 +- Xorg/src/main/java/io/neoterm/Settings.java | 1428 ++-- .../main/java/io/neoterm/SettingsMenu.java | 315 +- .../java/io/neoterm/SettingsMenuKeyboard.java | 1494 ++-- .../java/io/neoterm/SettingsMenuMisc.java | 1027 ++- .../java/io/neoterm/SettingsMenuMouse.java | 1327 ++-- Xorg/src/main/java/io/neoterm/Video.java | 1928 +++-- .../main/java/io/neoterm/XZInputStream.java | 211 +- .../io/neoterm/xorg/NeoXorgViewClient.java | 33 +- Xorg/src/main/res/values-zh/strings.xml | 336 +- Xorg/src/main/res/values/dimen.xml | 4 +- Xorg/src/main/res/values/ids.xml | 8 +- Xorg/src/main/res/values/strings.xml | 342 +- Xorg/src/main/res/xml/amiga.xml | 95 +- Xorg/src/main/res/xml/amiga_alt.xml | 104 +- Xorg/src/main/res/xml/amiga_alt_shift.xml | 104 +- Xorg/src/main/res/xml/amiga_old.xml | 170 +- Xorg/src/main/res/xml/amiga_shift.xml | 100 +- Xorg/src/main/res/xml/atari800.xml | 162 +- Xorg/src/main/res/xml/c64.xml | 162 +- Xorg/src/main/res/xml/qwerty.xml | 100 +- Xorg/src/main/res/xml/qwerty_alt.xml | 104 +- Xorg/src/main/res/xml/qwerty_alt_shift.xml | 104 +- Xorg/src/main/res/xml/qwerty_shift.xml | 100 +- .../test/java/io/neoterm/ExampleUnitTest.java | 10 +- app/build.gradle | 110 +- app/src/main/AndroidManifest.xml | 338 +- app/src/main/java/io/neoterm/App.kt | 106 +- .../java/io/neoterm/backend/ByteQueue.java | 200 +- .../io/neoterm/backend/EmulatorDebug.java | 6 +- app/src/main/java/io/neoterm/backend/JNI.java | 60 +- .../java/io/neoterm/backend/KeyHandler.java | 535 +- .../io/neoterm/backend/TerminalBuffer.java | 790 +- .../neoterm/backend/TerminalColorScheme.java | 194 +- .../io/neoterm/backend/TerminalColors.java | 154 +- .../io/neoterm/backend/TerminalEmulator.java | 4824 ++++++------ .../io/neoterm/backend/TerminalOutput.java | 42 +- .../java/io/neoterm/backend/TerminalRow.java | 448 +- .../io/neoterm/backend/TerminalSession.java | 592 +- .../java/io/neoterm/backend/TextStyle.java | 132 +- .../main/java/io/neoterm/backend/WcWidth.java | 882 +-- .../io/neoterm/component/NeoInitializer.kt | 36 +- .../component/codegen/CodeGenComponent.kt | 20 +- .../codegen/generators/NeoColorGenerator.kt | 86 +- .../codegen/generators/NeoProfileGenerator.kt | 12 +- .../codegen/interfaces/CodeGenObject.kt | 2 +- .../codegen/interfaces/CodeGenerator.kt | 4 +- .../colorscheme/ColorSchemeComponent.kt | 197 +- .../colorscheme/DefaultColorScheme.kt | 14 +- .../component/colorscheme/NeoColorScheme.kt | 308 +- .../completion/CompletionComponent.kt | 16 +- .../provider/FileCompletionProvider.kt | 74 +- .../provider/ProgramCompletionProvider.kt | 10 +- .../component/config/ConfigureComponent.kt | 30 +- .../component/config/IConfigureLoader.kt | 2 +- .../config/loaders/NeoLangConfigureLoader.kt | 8 +- .../loaders/OldColorSchemeConfigureFile.kt | 50 +- .../config/loaders/OldConfigureLoader.kt | 18 +- .../loaders/OldExtraKeysConfigureFile.kt | 200 +- .../component/extrakey/ExtraKeyComponent.kt | 86 +- .../neoterm/component/extrakey/NeoExtraKey.kt | 152 +- .../neoterm/component/font/FontComponent.kt | 206 +- .../java/io/neoterm/component/font/NeoFont.kt | 46 +- .../io/neoterm/component/pm/Architecture.kt | 22 +- .../io/neoterm/component/pm/NeoPackageInfo.kt | 30 +- .../component/pm/NeoPackageParser.java | 284 +- .../component/pm/PackageComponent.java | 186 +- .../java/io/neoterm/component/pm/Source.java | 26 +- .../io/neoterm/component/pm/SourceHelper.kt | 112 +- .../io/neoterm/component/pm/SourceManager.kt | 64 +- .../neoterm/component/profile/NeoProfile.kt | 94 +- .../component/profile/ProfileComponent.kt | 90 +- .../component/session/SessionComponent.kt | 158 +- .../userscript/UserScriptComponent.kt | 68 +- .../io/neoterm/framework/NeoTermDatabase.java | 1237 ++- .../framework/database/DatabaseDataType.java | 56 +- .../database/NeoTermSQLiteConfig.java | 206 +- .../database/OnDatabaseUpgradedListener.java | 12 +- .../database/SQLStatementHelper.java | 336 +- .../framework/database/SQLTypeParser.java | 130 +- .../framework/database/TableHelper.java | 214 +- .../framework/database/ValueHelper.java | 204 +- .../framework/database/annotation/ID.java | 12 +- .../framework/database/annotation/Table.java | 16 +- .../framework/database/bean/TableInfo.java | 54 +- .../framework/reflection/NullPointer.java | 1 + .../neoterm/framework/reflection/Reflect.java | 1008 +-- .../reflection/ReflectionException.java | 6 +- .../frontend/completion/CompletionManager.kt | 46 +- .../frontend/completion/ProviderDetector.kt | 24 +- .../completion/listener/MarkScoreListener.kt | 2 +- .../listener/OnAutoCompleteListener.kt | 8 +- .../listener/OnCandidateSelectedListener.kt | 2 +- .../completion/model/CompletionCandidate.kt | 4 +- .../completion/model/CompletionResult.kt | 12 +- .../completion/provider/ICandidateProvider.kt | 6 +- .../completion/view/CandidatePopupWindow.kt | 208 +- .../frontend/completion/view/MaxHeightView.kt | 78 +- .../frontend/component/ComponentManager.kt | 56 +- .../frontend/component/NeoComponent.kt | 6 +- .../helper/ConfigFileBasedComponent.kt | 74 +- .../component/helper/ConfigFileBasedObject.kt | 4 +- .../neoterm/frontend/config/DefaultValues.kt | 32 +- .../frontend/config/NeoConfigureFile.kt | 22 +- .../neoterm/frontend/config/NeoPermission.kt | 72 +- .../neoterm/frontend/config/NeoPreference.kt | 440 +- .../io/neoterm/frontend/config/NeoTermPath.kt | 42 +- .../frontend/floating/TerminalDialog.kt | 146 +- .../frontend/floating/WindowTermView.kt | 36 +- .../java/io/neoterm/frontend/logging/NLog.kt | 545 +- .../frontend/session/shell/ShellParameter.kt | 96 +- .../frontend/session/shell/ShellProfile.kt | 122 +- .../session/shell/ShellTermSession.kt | 442 +- .../shell/client/BasicSessionCallback.kt | 32 +- .../session/shell/client/BasicViewClient.kt | 74 +- .../session/shell/client/BellController.kt | 50 +- .../shell/client/TermCompleteListener.kt | 245 +- .../shell/client/TermSessionCallback.kt | 76 +- .../session/shell/client/TermSessionData.kt | 74 +- .../session/shell/client/TermUiPresenter.kt | 22 +- .../session/shell/client/TermViewClient.kt | 430 +- .../neoterm/frontend/session/xorg/XSession.kt | 651 +- .../session/xorg/client/XSessionData.kt | 18 +- .../terminal/GestureAndScaleRecognizer.kt | 184 +- .../frontend/terminal/TerminalRenderer.java | 411 +- .../frontend/terminal/TerminalView.java | 2031 +++-- .../frontend/terminal/TerminalViewClient.java | 33 +- .../terminal/extrakey/ExtraKeysView.kt | 400 +- .../terminal/extrakey/button/IExtraButton.kt | 130 +- .../extrakey/button/RepeatableButton.kt | 78 +- .../extrakey/button/StatedControlButton.kt | 71 +- .../terminal/extrakey/button/TextButton.kt | 28 +- .../extrakey/combine/CombinedSequence.kt | 30 +- .../io/neoterm/services/NeoTermService.kt | 355 +- .../java/io/neoterm/setup/ResultListener.kt | 2 +- .../main/java/io/neoterm/setup/SetupHelper.kt | 114 +- .../java/io/neoterm/setup/SetupThread.java | 277 +- .../io/neoterm/setup/SourceConnection.java | 6 +- .../setup/connections/NetworkConnection.kt | 48 +- .../setup/connections/OfflineConnection.kt | 54 +- .../setup/connections/OfflineUriConnection.kt | 8 +- .../java/io/neoterm/ui/bonus/BonusActivity.kt | 283 +- .../java/io/neoterm/ui/crash/CrashActivity.kt | 76 +- .../ui/customize/BaseCustomizeActivity.kt | 64 +- .../ui/customize/ColorSchemeActivity.kt | 277 +- .../neoterm/ui/customize/CustomizeActivity.kt | 232 +- .../ui/customize/adapter/ColorItemAdapter.kt | 49 +- .../adapter/holder/ColorItemViewHolder.kt | 27 +- .../neoterm/ui/customize/model/ColorItem.kt | 24 +- .../neoterm/ui/pm/PackageManagerActivity.kt | 451 +- .../neoterm/ui/pm/adapter/PackageAdapter.kt | 32 +- .../ui/pm/adapter/holder/PackageViewHolder.kt | 17 +- .../io/neoterm/ui/pm/model/PackageModel.kt | 32 +- .../neoterm/ui/pm/utils/StringDistance.java | 56 +- .../neoterm/ui/pm/view/RecyclerTabLayout.java | 1099 ++- .../ui/settings/BasePreferenceActivity.kt | 146 +- .../ui/settings/GeneralSettingsActivity.kt | 96 +- .../io/neoterm/ui/settings/SettingActivity.kt | 28 +- .../neoterm/ui/settings/UISettingsActivity.kt | 28 +- .../java/io/neoterm/ui/setup/SetupActivity.kt | 410 +- .../io/neoterm/ui/support/AboutActivity.kt | 238 +- .../java/io/neoterm/ui/support/Donation.kt | 48 +- .../io/neoterm/ui/support/HelpActivity.kt | 26 +- .../io/neoterm/ui/term/NeoTermActivity.kt | 1472 ++-- .../neoterm/ui/term/NeoTermRemoteInterface.kt | 371 +- .../java/io/neoterm/ui/term/SessionRemover.kt | 42 +- .../java/io/neoterm/ui/term/tab/NeoTab.kt | 14 +- .../io/neoterm/ui/term/tab/NeoTabDecorator.kt | 293 +- .../java/io/neoterm/ui/term/tab/TermTab.kt | 144 +- .../io/neoterm/ui/term/tab/XSessionTab.kt | 52 +- .../main/java/io/neoterm/utils/AssetsUtils.kt | 20 +- .../java/io/neoterm/utils/CrashHandler.kt | 26 +- .../main/java/io/neoterm/utils/FileUtils.kt | 32 +- .../java/io/neoterm/utils/FullScreenHelper.kt | 204 +- .../main/java/io/neoterm/utils/MediaUtils.kt | 218 +- .../java/io/neoterm/utils/PackageUtils.kt | 30 +- .../main/java/io/neoterm/utils/RangedInt.kt | 24 +- .../java/io/neoterm/utils/TerminalUtils.kt | 82 +- app/src/main/res/drawable/ic_description.xml | 14 +- app/src/main/res/drawable/ic_donate.xml | 14 +- app/src/main/res/drawable/ic_github.xml | 14 +- app/src/main/res/drawable/ic_info.xml | 14 +- app/src/main/res/drawable/ic_neoterm.xml | 40 +- app/src/main/res/drawable/ic_new_session.xml | 20 +- app/src/main/res/drawable/ic_person.xml | 14 +- .../main/res/drawable/ic_terminal_running.xml | 34 +- .../text_select_handle_left_material.xml | 2 +- .../text_select_handle_right_material.xml | 2 +- app/src/main/res/layout/dialog_edit_text.xml | 32 +- .../main/res/layout/dialog_edit_two_text.xml | 56 +- app/src/main/res/layout/item_color.xml | 76 +- .../res/layout/item_complete_candidate.xml | 48 +- app/src/main/res/layout/item_package.xml | 46 +- .../res/layout/layout_pm_package_list.xml | 24 +- .../main/res/layout/popup_auto_complete.xml | 16 +- app/src/main/res/layout/ui_about.xml | 710 +- app/src/main/res/layout/ui_color_scheme.xml | 120 +- .../main/res/layout/ui_command_shortcut.xml | 68 +- app/src/main/res/layout/ui_crash.xml | 106 +- app/src/main/res/layout/ui_customize.xml | 220 +- app/src/main/res/layout/ui_faq.xml | 184 +- app/src/main/res/layout/ui_main.xml | 50 +- app/src/main/res/layout/ui_pm.xml | 54 +- app/src/main/res/layout/ui_pm_single_tab.xml | 26 +- app/src/main/res/layout/ui_setup.xml | 188 +- app/src/main/res/layout/ui_term.xml | 40 +- app/src/main/res/layout/ui_term_dialog.xml | 24 +- app/src/main/res/layout/ui_term_embedded.xml | 22 +- .../main/res/layout/ui_user_script_list.xml | 66 +- app/src/main/res/layout/ui_xorg.xml | 14 +- app/src/main/res/menu/menu_color_editor.xml | 12 +- app/src/main/res/menu/menu_main.xml | 74 +- app/src/main/res/menu/menu_pm.xml | 46 +- app/src/main/res/values-zh-rCN/strings.xml | 399 +- app/src/main/res/values-zh-rTW/strings.xml | 206 +- app/src/main/res/values/attrs.xml | 32 +- app/src/main/res/values/colors.xml | 18 +- app/src/main/res/values/dimens.xml | 40 +- app/src/main/res/values/preference_keys.xml | 34 +- app/src/main/res/values/shortcut_configs.xml | 2 +- app/src/main/res/values/strings.xml | 437 +- app/src/main/res/values/styles.xml | 78 +- app/src/main/res/xml/app_shortcuts.xml | 24 +- app/src/main/res/xml/backup_config.xml | 4 +- app/src/main/res/xml/setting_general.xml | 92 +- app/src/main/res/xml/settings_main.xml | 78 +- app/src/main/res/xml/settings_ui.xml | 50 +- build.gradle | 78 +- chrome-tabs/build.gradle | 32 +- .../mrapp/android/tabswitcher/Animation.java | 220 +- .../de/mrapp/android/tabswitcher/Layout.java | 24 +- .../android/tabswitcher/LayoutPolicy.java | 102 +- .../android/tabswitcher/PeekAnimation.java | 180 +- .../android/tabswitcher/RevealAnimation.java | 180 +- .../android/tabswitcher/SwipeAnimation.java | 168 +- .../de/mrapp/android/tabswitcher/Tab.java | 1016 ++- .../android/tabswitcher/TabCloseListener.java | 22 +- .../tabswitcher/TabPreviewListener.java | 24 +- .../android/tabswitcher/TabSwitcher.java | 2638 ++++--- .../tabswitcher/TabSwitcherDecorator.java | 371 +- .../tabswitcher/TabSwitcherListener.java | 144 +- .../drawable/TabSwitcherDrawable.java | 283 +- .../iterator/AbstractTabItemIterator.java | 338 +- .../iterator/ArrayTabItemIterator.java | 147 +- .../tabswitcher/iterator/TabItemIterator.java | 151 +- .../layout/AbstractDragHandler.java | 1307 ++-- .../layout/AbstractTabSwitcherLayout.java | 1034 ++- .../layout/AbstractTabViewHolder.java | 17 +- .../tabswitcher/layout/Arithmetics.java | 391 +- .../layout/ChildRecyclerAdapter.java | 181 +- .../tabswitcher/layout/TabSwitcherLayout.java | 73 +- .../layout/phone/PhoneArithmetics.java | 681 +- .../layout/phone/PhoneDragHandler.java | 438 +- .../layout/phone/PhoneRecyclerAdapter.java | 1427 ++-- .../layout/phone/PhoneTabSwitcherLayout.java | 6841 ++++++++--------- .../layout/phone/PhoneTabViewHolder.java | 41 +- .../layout/phone/PreviewDataBinder.java | 145 +- .../android/tabswitcher/model/Model.java | 1456 ++-- .../android/tabswitcher/model/Restorable.java | 30 +- .../android/tabswitcher/model/State.java | 40 +- .../android/tabswitcher/model/TabItem.java | 499 +- .../tabswitcher/model/TabSwitcherModel.java | 2338 +++--- .../mrapp/android/tabswitcher/model/Tag.java | 188 +- .../tabswitcher/view/TabSwitcherButton.java | 203 +- .../src/main/res/layout-land/phone_tab.xml | 98 +- chrome-tabs/src/main/res/layout/phone_tab.xml | 98 +- .../src/main/res/layout/phone_toolbar.xml | 18 +- .../res/layout/tab_switcher_menu_item.xml | 6 +- .../src/main/res/values-v21/styles.xml | 14 +- chrome-tabs/src/main/res/values/attrs.xml | 30 +- chrome-tabs/src/main/res/values/colors.xml | 6 +- chrome-tabs/src/main/res/values/dimens.xml | 34 +- chrome-tabs/src/main/res/values/ids.xml | 4 +- chrome-tabs/src/main/res/values/integers.xml | 28 +- chrome-tabs/src/main/res/values/styles.xml | 14 +- gradle.properties | 4 - 326 files changed, 37875 insertions(+), 38821 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 3b19cf4..5e5a1f3 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,8 +2,12 @@ ## Our Pledge -Originally, NeoTerm was designed as the front end of [Termux](https://github.com/termux/termux-app) to provide some functions that Termux didn't have, but we found it very convenient. In continuous development, we discovered our goal: to be the best terminal for Android. -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. +Originally, NeoTerm was designed as the front end of [Termux](https://github.com/termux/termux-app) to provide some +functions that Termux didn't have, but we found it very convenient. In continuous development, we discovered our goal: +to be the best terminal for Android. In the interest of fostering an open and welcoming environment, we as contributors +and maintainers pledge to making participation in our project and our community a harassment-free experience for +everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards @@ -26,23 +30,35 @@ Examples of unacceptable behavior by participants include: ## Our Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take +appropriate and fair corrective action in response to any instances of unacceptable behavior. -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, +issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the +project or its community. Examples of representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed representative at an online or offline +event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at kiva515@foxmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at +kiva515@foxmail.com. The project team will review and investigate all complaints, and will respond in a way that it +deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the +reporter of an incident. Further details of specific enforcement policies may be posted separately. -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent +repercussions as determined by other members of the project's leadership. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available +at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org + [version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4dd6b14..98b26e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,6 @@ # Contributing # How to + NeoTerm is a free software, so any contributions and any contribution types are welcomed! diff --git a/NeoLang/build.gradle b/NeoLang/build.gradle index 35c13da..6bfc897 100644 --- a/NeoLang/build.gradle +++ b/NeoLang/build.gradle @@ -2,31 +2,31 @@ apply plugin: 'java-library' apply plugin: 'kotlin' dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - compile rootProject.ext.deps["kotlin-stdlib"] + implementation fileTree(dir: 'libs', include: ['*.jar']) + compile rootProject.ext.deps["kotlin-stdlib"] } buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath rootProject.ext.deps["kotlin-gradle-plugin"] - } + repositories { + mavenCentral() + } + dependencies { + classpath rootProject.ext.deps["kotlin-gradle-plugin"] + } } repositories { - mavenCentral() + mavenCentral() } compileKotlin { - kotlinOptions { - jvmTarget = "1.8" - } + kotlinOptions { + jvmTarget = "1.8" + } } compileTestKotlin { - kotlinOptions { - jvmTarget = "1.8" - } + kotlinOptions { + jvmTarget = "1.8" + } } dependencies { - testImplementation rootProject.ext.deps["junit"] + testImplementation rootProject.ext.deps["junit"] } diff --git a/NeoLang/src/main/java/io/neolang/ast/NeoLangToken.kt b/NeoLang/src/main/java/io/neolang/ast/NeoLangToken.kt index 5077f0b..81d7272 100644 --- a/NeoLang/src/main/java/io/neolang/ast/NeoLangToken.kt +++ b/NeoLang/src/main/java/io/neolang/ast/NeoLangToken.kt @@ -5,9 +5,9 @@ package io.neolang.ast */ open class NeoLangToken(val tokenType: NeoLangTokenType, val tokenValue: NeoLangTokenValue) { - var lineNumber = 0 + var lineNumber = 0 - override fun toString(): String { - return "Token { tokenType: $tokenType, tokenValue: $tokenValue };" - } + override fun toString(): String { + return "Token { tokenType: $tokenType, tokenValue: $tokenValue };" + } } diff --git a/NeoLang/src/main/java/io/neolang/ast/NeoLangTokenType.kt b/NeoLang/src/main/java/io/neolang/ast/NeoLangTokenType.kt index d61ab9c..d70d5e8 100644 --- a/NeoLang/src/main/java/io/neolang/ast/NeoLangTokenType.kt +++ b/NeoLang/src/main/java/io/neolang/ast/NeoLangTokenType.kt @@ -5,15 +5,15 @@ package io.neolang.ast */ enum class NeoLangTokenType { - NUMBER, - ID, - STRING, - BRACKET_START, - BRACKET_END, - ARRAY_START, - ARRAY_END, - COLON, - COMMA, - EOL, - EOF, + NUMBER, + ID, + STRING, + BRACKET_START, + BRACKET_END, + ARRAY_START, + ARRAY_END, + COLON, + COMMA, + EOL, + EOF, } diff --git a/NeoLang/src/main/java/io/neolang/ast/NeoLangTokenValue.kt b/NeoLang/src/main/java/io/neolang/ast/NeoLangTokenValue.kt index 8d36d6a..4ccb111 100644 --- a/NeoLang/src/main/java/io/neolang/ast/NeoLangTokenValue.kt +++ b/NeoLang/src/main/java/io/neolang/ast/NeoLangTokenValue.kt @@ -7,32 +7,32 @@ import io.neolang.runtime.type.NeoLangValue */ class NeoLangTokenValue(val value: NeoLangValue) { - override fun toString(): String { - return value.asString() - } - - companion object { - val COLON = NeoLangTokenValue(NeoLangValue(":")) - val COMMA = NeoLangTokenValue(NeoLangValue(",")) - val QUOTE = NeoLangTokenValue(NeoLangValue("\"")) - val EOF = NeoLangTokenValue(NeoLangValue("")) - - val BRACKET_START = NeoLangTokenValue(NeoLangValue("{")) - val BRACKET_END = NeoLangTokenValue(NeoLangValue("}")) - val ARRAY_START = NeoLangTokenValue(NeoLangValue("[")) - val ARRAY_END = NeoLangTokenValue(NeoLangValue("]")) - - fun wrap(tokenText: String): NeoLangTokenValue { - return when (tokenText) { - COLON.value.asString() -> COLON - COMMA.value.asString() -> COMMA - QUOTE.value.asString() -> QUOTE - BRACKET_START.value.asString() -> BRACKET_START - BRACKET_END.value.asString() -> BRACKET_END - ARRAY_START.value.asString() -> ARRAY_START - ARRAY_END.value.asString() -> ARRAY_END - else -> NeoLangTokenValue(NeoLangValue(tokenText)) - } - } + override fun toString(): String { + return value.asString() + } + + companion object { + val COLON = NeoLangTokenValue(NeoLangValue(":")) + val COMMA = NeoLangTokenValue(NeoLangValue(",")) + val QUOTE = NeoLangTokenValue(NeoLangValue("\"")) + val EOF = NeoLangTokenValue(NeoLangValue("")) + + val BRACKET_START = NeoLangTokenValue(NeoLangValue("{")) + val BRACKET_END = NeoLangTokenValue(NeoLangValue("}")) + val ARRAY_START = NeoLangTokenValue(NeoLangValue("[")) + val ARRAY_END = NeoLangTokenValue(NeoLangValue("]")) + + fun wrap(tokenText: String): NeoLangTokenValue { + return when (tokenText) { + COLON.value.asString() -> COLON + COMMA.value.asString() -> COMMA + QUOTE.value.asString() -> QUOTE + BRACKET_START.value.asString() -> BRACKET_START + BRACKET_END.value.asString() -> BRACKET_END + ARRAY_START.value.asString() -> ARRAY_START + ARRAY_END.value.asString() -> ARRAY_END + else -> NeoLangTokenValue(NeoLangValue(tokenText)) + } } + } } diff --git a/NeoLang/src/main/java/io/neolang/ast/base/NeoLangAst.kt b/NeoLang/src/main/java/io/neolang/ast/base/NeoLangAst.kt index 33eea61..3f080be 100644 --- a/NeoLang/src/main/java/io/neolang/ast/base/NeoLangAst.kt +++ b/NeoLang/src/main/java/io/neolang/ast/base/NeoLangAst.kt @@ -6,7 +6,7 @@ import io.neolang.ast.visitor.VisitorFactory * @author kiva */ open class NeoLangAst { - fun visit(): VisitorFactory { - return VisitorFactory(this) - } + fun visit(): VisitorFactory { + return VisitorFactory(this) + } } \ No newline at end of file diff --git a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangArrayNode.kt b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangArrayNode.kt index 21d62e8..458fd6c 100644 --- a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangArrayNode.kt +++ b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangArrayNode.kt @@ -6,7 +6,7 @@ import io.neolang.ast.base.NeoLangBaseNode * @author kiva */ class NeoLangArrayNode(val arrayNameNode: NeoLangStringNode, val elements: Array) : NeoLangBaseNode() { - companion object { - class ArrayElement(val index: Int, val block: NeoLangBlockNode) - } + companion object { + class ArrayElement(val index: Int, val block: NeoLangBlockNode) + } } \ No newline at end of file diff --git a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangAstBasedNode.kt b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangAstBasedNode.kt index f0392b0..f98847f 100644 --- a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangAstBasedNode.kt +++ b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangAstBasedNode.kt @@ -6,7 +6,7 @@ import io.neolang.ast.base.NeoLangBaseNode * @author kiva */ open class NeoLangAstBasedNode(val ast: NeoLangBaseNode) : NeoLangBaseNode() { - override fun toString(): String { - return "${javaClass.simpleName} { ast: $ast }" - } + override fun toString(): String { + return "${javaClass.simpleName} { ast: $ast }" + } } \ No newline at end of file diff --git a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangAttributeNode.kt b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangAttributeNode.kt index 06632f5..45843e4 100644 --- a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangAttributeNode.kt +++ b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangAttributeNode.kt @@ -7,7 +7,7 @@ import io.neolang.ast.base.NeoLangBaseNode */ class NeoLangAttributeNode(val stringNode: NeoLangStringNode, val blockNode: NeoLangBlockNode) : NeoLangBaseNode() { - override fun toString(): String { - return "NeoLangAttributeNode { stringNode: $stringNode, block: $blockNode }" - } + override fun toString(): String { + return "NeoLangAttributeNode { stringNode: $stringNode, block: $blockNode }" + } } diff --git a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangBlockNode.kt b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangBlockNode.kt index ac33467..d666465 100644 --- a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangBlockNode.kt +++ b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangBlockNode.kt @@ -6,9 +6,9 @@ import io.neolang.ast.base.NeoLangBaseNode * @author kiva */ class NeoLangBlockNode(blockElement: NeoLangBaseNode) : NeoLangAstBasedNode(blockElement) { - companion object { - fun emptyNode(): NeoLangBlockNode { - return NeoLangBlockNode(NeoLangDummyNode()) - } + companion object { + fun emptyNode(): NeoLangBlockNode { + return NeoLangBlockNode(NeoLangDummyNode()) } + } } \ No newline at end of file diff --git a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangGroupNode.kt b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangGroupNode.kt index bf16c57..b4771bd 100644 --- a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangGroupNode.kt +++ b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangGroupNode.kt @@ -7,13 +7,13 @@ import io.neolang.ast.base.NeoLangBaseNode */ class NeoLangGroupNode(val attributes: Array) : NeoLangBaseNode() { - override fun toString(): String { - return "NeoLangGroupNode { attrs: $attributes }" - } + override fun toString(): String { + return "NeoLangGroupNode { attrs: $attributes }" + } - companion object { - fun emptyNode() : NeoLangGroupNode { - return NeoLangGroupNode(arrayOf()) - } + companion object { + fun emptyNode(): NeoLangGroupNode { + return NeoLangGroupNode(arrayOf()) } + } } \ No newline at end of file diff --git a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangProgramNode.kt b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangProgramNode.kt index feb9146..10e73df 100644 --- a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangProgramNode.kt +++ b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangProgramNode.kt @@ -8,14 +8,14 @@ import io.neolang.ast.base.NeoLangBaseNode class NeoLangProgramNode(val groups: List) : NeoLangBaseNode() { - override fun toString(): String { - return "NeoLangProgramNode { groups: $groups }" - } + override fun toString(): String { + return "NeoLangProgramNode { groups: $groups }" + } - companion object { - fun emptyNode() : NeoLangProgramNode { - return NeoLangProgramNode(listOf()) - } + companion object { + fun emptyNode(): NeoLangProgramNode { + return NeoLangProgramNode(listOf()) } + } } diff --git a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangTokenBasedNode.kt b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangTokenBasedNode.kt index 9ad1828..b89d793 100644 --- a/NeoLang/src/main/java/io/neolang/ast/node/NeoLangTokenBasedNode.kt +++ b/NeoLang/src/main/java/io/neolang/ast/node/NeoLangTokenBasedNode.kt @@ -8,11 +8,11 @@ import io.neolang.runtime.type.NeoLangValue * @author kiva */ open class NeoLangTokenBasedNode(val token: NeoLangToken) : NeoLangBaseNode() { - override fun toString(): String { - return "${javaClass.simpleName} { token: $token }" - } + override fun toString(): String { + return "${javaClass.simpleName} { token: $token }" + } - fun eval(): NeoLangValue { - return token.tokenValue.value - } + fun eval(): NeoLangValue { + return token.tokenValue.value + } } \ No newline at end of file diff --git a/NeoLang/src/main/java/io/neolang/ast/visitor/AstVisitor.kt b/NeoLang/src/main/java/io/neolang/ast/visitor/AstVisitor.kt index ed22029..daccf73 100644 --- a/NeoLang/src/main/java/io/neolang/ast/visitor/AstVisitor.kt +++ b/NeoLang/src/main/java/io/neolang/ast/visitor/AstVisitor.kt @@ -6,12 +6,12 @@ import io.neolang.ast.base.NeoLangAst * @author kiva */ class AstVisitor(private val ast: NeoLangAst, private val visitorCallback: IVisitorCallback) { - fun start() { - AstVisitorImpl.visitStartAst(ast, visitorCallback) - } + fun start() { + AstVisitorImpl.visitStartAst(ast, visitorCallback) + } - @Suppress("UNCHECKED_CAST") - fun getCallback() : T { - return visitorCallback as T - } + @Suppress("UNCHECKED_CAST") + fun getCallback(): T { + return visitorCallback as T + } } diff --git a/NeoLang/src/main/java/io/neolang/ast/visitor/AstVisitorImpl.kt b/NeoLang/src/main/java/io/neolang/ast/visitor/AstVisitorImpl.kt index c3337e4..0475497 100644 --- a/NeoLang/src/main/java/io/neolang/ast/visitor/AstVisitorImpl.kt +++ b/NeoLang/src/main/java/io/neolang/ast/visitor/AstVisitorImpl.kt @@ -18,87 +18,87 @@ import io.neolang.runtime.type.NeoLangValue * @author kiva */ internal object AstVisitorImpl { - fun visitProgram(ast: NeoLangProgramNode, visitorCallback: IVisitorCallback) { - visitorCallback.onStart() - ast.groups.forEach { visitGroup(it, visitorCallback) } - visitorCallback.onFinish() + fun visitProgram(ast: NeoLangProgramNode, visitorCallback: IVisitorCallback) { + visitorCallback.onStart() + ast.groups.forEach { visitGroup(it, visitorCallback) } + visitorCallback.onFinish() + } + + fun visitGroup(ast: NeoLangGroupNode, visitorCallback: IVisitorCallback) { + ast.attributes.forEach { + visitAttribute(it, visitorCallback) } + } - fun visitGroup(ast: NeoLangGroupNode, visitorCallback: IVisitorCallback) { - ast.attributes.forEach { - visitAttribute(it, visitorCallback) - } - } + fun visitAttribute(ast: NeoLangAttributeNode, visitorCallback: IVisitorCallback) { + visitBlock(ast.blockNode, ast.stringNode.eval().asString(), visitorCallback) + } - fun visitAttribute(ast: NeoLangAttributeNode, visitorCallback: IVisitorCallback) { - visitBlock(ast.blockNode, ast.stringNode.eval().asString(), visitorCallback) - } + fun visitArray(ast: NeoLangArrayNode, visitorCallback: IVisitorCallback) { + val arrayName = ast.arrayNameNode.eval().asString() - fun visitArray(ast: NeoLangArrayNode, visitorCallback: IVisitorCallback) { - val arrayName = ast.arrayNameNode.eval().asString() - - visitorCallback.onEnterContext(arrayName) - ast.elements.forEach { - AstVisitorImpl.visitArrayElementBlock(it.block, it.index, visitorCallback) + visitorCallback.onEnterContext(arrayName) + ast.elements.forEach { + AstVisitorImpl.visitArrayElementBlock(it.block, it.index, visitorCallback) // AstVisitorImpl.visitBlock(it.block, it.index.toString(), visitorCallback) - } + } + visitorCallback.onExitContext() + } + + fun visitArrayElementBlock(ast: NeoLangBlockNode, index: Int, visitorCallback: IVisitorCallback) { + val visitingNode = ast.ast + when (visitingNode) { + is NeoLangGroupNode -> { + // is a sub block, e.g. + // block: { $blockName: {} } + visitorCallback.onEnterContext(index.toString()) + AstVisitorImpl.visitGroup(visitingNode, visitorCallback) visitorCallback.onExitContext() + } + is NeoLangStringNode -> { + definePrimaryData(index.toString(), visitingNode.eval(), visitorCallback) + } + is NeoLangNumberNode -> { + definePrimaryData(index.toString(), visitingNode.eval(), visitorCallback) + } } + } - fun visitArrayElementBlock(ast: NeoLangBlockNode, index: Int, visitorCallback: IVisitorCallback) { - val visitingNode = ast.ast - when (visitingNode) { - is NeoLangGroupNode -> { - // is a sub block, e.g. - // block: { $blockName: {} } - visitorCallback.onEnterContext(index.toString()) - AstVisitorImpl.visitGroup(visitingNode, visitorCallback) - visitorCallback.onExitContext() - } - is NeoLangStringNode -> { - definePrimaryData(index.toString(), visitingNode.eval(), visitorCallback) - } - is NeoLangNumberNode -> { - definePrimaryData(index.toString(), visitingNode.eval(), visitorCallback) - } - } + fun visitBlock(ast: NeoLangBlockNode, blockName: String, visitorCallback: IVisitorCallback) { + val visitingNode = ast.ast + when (visitingNode) { + is NeoLangGroupNode -> { + // is a sub block, e.g. + // block: { $blockName: {} } + + visitorCallback.onEnterContext(blockName) + AstVisitorImpl.visitGroup(visitingNode, visitorCallback) + visitorCallback.onExitContext() + } + is NeoLangArrayNode -> { + // array: [ "a", "b", "c", 1, 2, 3 ] + AstVisitorImpl.visitArray(visitingNode, visitorCallback) + } + is NeoLangStringNode -> { + // block: { $blockName: "hello" } + definePrimaryData(blockName, visitingNode.eval(), visitorCallback) + } + is NeoLangNumberNode -> { + // block: { $blockName: 123.456 } + definePrimaryData(blockName, visitingNode.eval(), visitorCallback) + } } + } - fun visitBlock(ast: NeoLangBlockNode, blockName: String, visitorCallback: IVisitorCallback) { - val visitingNode = ast.ast - when (visitingNode) { - is NeoLangGroupNode -> { - // is a sub block, e.g. - // block: { $blockName: {} } + private fun definePrimaryData(name: String, value: NeoLangValue, visitorCallback: IVisitorCallback) { + visitorCallback.getCurrentContext().defineAttribute(name, value) + } - visitorCallback.onEnterContext(blockName) - AstVisitorImpl.visitGroup(visitingNode, visitorCallback) - visitorCallback.onExitContext() - } - is NeoLangArrayNode -> { - // array: [ "a", "b", "c", 1, 2, 3 ] - AstVisitorImpl.visitArray(visitingNode, visitorCallback) - } - is NeoLangStringNode -> { - // block: { $blockName: "hello" } - definePrimaryData(blockName, visitingNode.eval(), visitorCallback) - } - is NeoLangNumberNode -> { - // block: { $blockName: 123.456 } - definePrimaryData(blockName, visitingNode.eval(), visitorCallback) - } - } - } - - private fun definePrimaryData(name: String, value: NeoLangValue, visitorCallback: IVisitorCallback) { - visitorCallback.getCurrentContext().defineAttribute(name, value) - } - - fun visitStartAst(ast: NeoLangAst, visitorCallback: IVisitorCallback) { - when (ast) { - is NeoLangProgramNode -> AstVisitorImpl.visitProgram(ast, visitorCallback) - is NeoLangGroupNode -> AstVisitorImpl.visitGroup(ast, visitorCallback) - is NeoLangArrayNode -> AstVisitorImpl.visitArray(ast, visitorCallback) - } + fun visitStartAst(ast: NeoLangAst, visitorCallback: IVisitorCallback) { + when (ast) { + is NeoLangProgramNode -> AstVisitorImpl.visitProgram(ast, visitorCallback) + is NeoLangGroupNode -> AstVisitorImpl.visitGroup(ast, visitorCallback) + is NeoLangArrayNode -> AstVisitorImpl.visitArray(ast, visitorCallback) } + } } diff --git a/NeoLang/src/main/java/io/neolang/ast/visitor/IVisitorCallback.kt b/NeoLang/src/main/java/io/neolang/ast/visitor/IVisitorCallback.kt index d5d71c0..a83e295 100644 --- a/NeoLang/src/main/java/io/neolang/ast/visitor/IVisitorCallback.kt +++ b/NeoLang/src/main/java/io/neolang/ast/visitor/IVisitorCallback.kt @@ -6,13 +6,13 @@ import io.neolang.runtime.context.NeoLangContext * @author kiva */ interface IVisitorCallback { - fun onStart() + fun onStart() - fun onFinish() + fun onFinish() - fun onEnterContext(contextName: String) + fun onEnterContext(contextName: String) - fun onExitContext() + fun onExitContext() - fun getCurrentContext() : NeoLangContext + fun getCurrentContext(): NeoLangContext } diff --git a/NeoLang/src/main/java/io/neolang/ast/visitor/IVisitorCallbackAdapter.kt b/NeoLang/src/main/java/io/neolang/ast/visitor/IVisitorCallbackAdapter.kt index 27a30a8..8412efb 100644 --- a/NeoLang/src/main/java/io/neolang/ast/visitor/IVisitorCallbackAdapter.kt +++ b/NeoLang/src/main/java/io/neolang/ast/visitor/IVisitorCallbackAdapter.kt @@ -6,19 +6,19 @@ import io.neolang.runtime.context.NeoLangContext * @author kiva */ open class IVisitorCallbackAdapter : IVisitorCallback { - override fun onStart() { - } + override fun onStart() { + } - override fun onFinish() { - } + override fun onFinish() { + } - override fun onEnterContext(contextName: String) { - } + override fun onEnterContext(contextName: String) { + } - override fun onExitContext() { - } + override fun onExitContext() { + } - override fun getCurrentContext(): NeoLangContext { - throw RuntimeException("getCurrentContext() not supported in this IVisitorCallback!") - } + override fun getCurrentContext(): NeoLangContext { + throw RuntimeException("getCurrentContext() not supported in this IVisitorCallback!") + } } \ No newline at end of file diff --git a/NeoLang/src/main/java/io/neolang/ast/visitor/VisitorFactory.kt b/NeoLang/src/main/java/io/neolang/ast/visitor/VisitorFactory.kt index 1b1b11c..b6a8487 100644 --- a/NeoLang/src/main/java/io/neolang/ast/visitor/VisitorFactory.kt +++ b/NeoLang/src/main/java/io/neolang/ast/visitor/VisitorFactory.kt @@ -8,11 +8,11 @@ import io.neolang.ast.base.NeoLangAst class VisitorFactory(private val ast: NeoLangAst) { - fun getVisitor(callbackInterface: Class): AstVisitor? { - try { - return AstVisitor(ast, callbackInterface.newInstance()) - } catch (e: Exception) { - return null - } + fun getVisitor(callbackInterface: Class): AstVisitor? { + try { + return AstVisitor(ast, callbackInterface.newInstance()) + } catch (e: Exception) { + return null } + } } diff --git a/NeoLang/src/main/java/io/neolang/main/Main.kt b/NeoLang/src/main/java/io/neolang/main/Main.kt index 326de36..ad4ff49 100644 --- a/NeoLang/src/main/java/io/neolang/main/Main.kt +++ b/NeoLang/src/main/java/io/neolang/main/Main.kt @@ -8,33 +8,33 @@ import java.io.FileInputStream * @author kiva */ class Main { - companion object { - @JvmStatic - fun main(args: Array) { - if (args.isEmpty()) { - println("Usage: NeoLang ") - return - } + companion object { + @JvmStatic + fun main(args: Array) { + if (args.isEmpty()) { + println("Usage: NeoLang ") + return + } - val parser = NeoLangParser() - args.forEach { - val programCode = readFully(it) - parser.setInputSource(programCode) - val ast = parser.parse() - println("Compile `$it'") - ast.visit() - .getVisitor(DisplayProcessVisitor::class.java) - ?.start() - } - return - } - - private fun readFully(file: String): String { - FileInputStream(file).use { - val bytes = ByteArray(it.available()) - it.read(bytes) - return String(bytes) - } - } + val parser = NeoLangParser() + args.forEach { + val programCode = readFully(it) + parser.setInputSource(programCode) + val ast = parser.parse() + println("Compile `$it'") + ast.visit() + .getVisitor(DisplayProcessVisitor::class.java) + ?.start() + } + return } + + private fun readFully(file: String): String { + FileInputStream(file).use { + val bytes = ByteArray(it.available()) + it.read(bytes) + return String(bytes) + } + } + } } \ No newline at end of file diff --git a/NeoLang/src/main/java/io/neolang/parser/NeoLangLexer.kt b/NeoLang/src/main/java/io/neolang/parser/NeoLangLexer.kt index 6544fc9..7175877 100755 --- a/NeoLang/src/main/java/io/neolang/parser/NeoLangLexer.kt +++ b/NeoLang/src/main/java/io/neolang/parser/NeoLangLexer.kt @@ -4,7 +4,6 @@ import io.neolang.ast.NeoLangEOFToken import io.neolang.ast.NeoLangToken import io.neolang.ast.NeoLangTokenType import io.neolang.ast.NeoLangTokenValue -import java.util.* /** * grammar: [ @@ -19,254 +18,255 @@ import java.util.* * @author kiva */ class NeoLangLexer { - private var programCode: String? = null - private var currentPosition: Int = 0 - private var currentChar: Char = ' ' - private var lineNumber = 0 + private var programCode: String? = null + private var currentPosition: Int = 0 + private var currentChar: Char = ' ' + private var lineNumber = 0 - internal fun setInputSource(programCode: String?) { - this.programCode = programCode + internal fun setInputSource(programCode: String?) { + this.programCode = programCode + } + + internal fun lex(): List { + val programCode = this.programCode ?: return listOf() + val tokens = ArrayList() + currentPosition = 0 + lineNumber = 1 + + if (programCode.isNotEmpty()) { + currentChar = programCode[currentPosition] + + while (currentPosition < programCode.length) { + val token = nextToken + if (token is NeoLangEOFToken) { + break + } + tokens.add(token) + } + } + return tokens + } + + private fun moveToNextChar(eofThrow: Boolean = false): Boolean { + val programCode = this.programCode ?: return false + currentPosition++ + if (currentPosition >= programCode.length) { + if (eofThrow) { + throw InvalidTokenException("Unexpected EOF near `$currentChar' in line $lineNumber") + } + return false + } else { + currentChar = programCode[currentPosition] + return true + } + } + + private val nextToken: NeoLangToken + get() { + val programCode = this.programCode ?: return NeoLangEOFToken() + + while (currentChar == ' ' + || currentChar == '\t' + || currentChar == '\n' + || currentChar == '\r' + ) { + if (currentChar == '\n') { + ++lineNumber + } + // Skip white chars + if (!moveToNextChar()) { + return NeoLangEOFToken() + } + } + + if (currentPosition >= programCode.length) { + return NeoLangEOFToken() + } + + val currentToken = NeoLangTokenValue.wrap(currentChar.toString()) + val token: NeoLangToken = when (currentToken) { + NeoLangTokenValue.COLON -> { + moveToNextChar(eofThrow = true) + NeoLangToken(NeoLangTokenType.COLON, currentToken) + } + NeoLangTokenValue.BRACKET_START -> { + moveToNextChar(eofThrow = true) + NeoLangToken(NeoLangTokenType.BRACKET_START, currentToken) + } + NeoLangTokenValue.BRACKET_END -> { + moveToNextChar() + NeoLangToken(NeoLangTokenType.BRACKET_END, currentToken) + } + NeoLangTokenValue.ARRAY_START -> { + moveToNextChar() + NeoLangToken(NeoLangTokenType.ARRAY_START, currentToken) + } + NeoLangTokenValue.ARRAY_END -> { + moveToNextChar() + NeoLangToken(NeoLangTokenType.ARRAY_END, currentToken) + } + NeoLangTokenValue.COMMA -> { + moveToNextChar(eofThrow = true) + NeoLangToken(NeoLangTokenType.COMMA, currentToken) + } + NeoLangTokenValue.QUOTE -> { + NeoLangToken(NeoLangTokenType.STRING, NeoLangTokenValue.wrap(getNextTokenAsString())) + } + else -> { + if (currentChar.isNumber()) { + NeoLangToken(NeoLangTokenType.NUMBER, NeoLangTokenValue.wrap(getNextTokenAsNumber())) + } else if (isIdentifier(currentChar, true)) { + NeoLangToken(NeoLangTokenType.ID, NeoLangTokenValue.wrap(getNextTokenAsId())) + } else { + throw InvalidTokenException("Unexpected character near line $lineNumber: $currentChar") + } + } + } + + token.lineNumber = lineNumber + return token } - internal fun lex(): List { - val programCode = this.programCode ?: return listOf() - val tokens = ArrayList() - currentPosition = 0 - lineNumber = 1 + private fun getNextTokenAsString(): String { + // Skip start quote + // and a single quote is now allowed + moveToNextChar(eofThrow = true) + val builder = StringBuilder() - if (programCode.isNotEmpty()) { - currentChar = programCode[currentPosition] - - while (currentPosition < programCode.length) { - val token = nextToken - if (token is NeoLangEOFToken) { - break - } - tokens.add(token) - } - } - return tokens - } - - private fun moveToNextChar(eofThrow: Boolean = false): Boolean { - val programCode = this.programCode ?: return false - currentPosition++ - if (currentPosition >= programCode.length) { - if (eofThrow) { - throw InvalidTokenException("Unexpected EOF near `$currentChar' in line $lineNumber") - } - return false - } else { - currentChar = programCode[currentPosition] - return true - } - } - - private val nextToken: NeoLangToken - get() { - val programCode = this.programCode ?: return NeoLangEOFToken() - - while (currentChar == ' ' - || currentChar == '\t' - || currentChar == '\n' - || currentChar == '\r') { - if (currentChar == '\n') { - ++lineNumber - } - // Skip white chars - if (!moveToNextChar()) { - return NeoLangEOFToken() - } - } - - if (currentPosition >= programCode.length) { - return NeoLangEOFToken() - } - - val currentToken = NeoLangTokenValue.wrap(currentChar.toString()) - val token: NeoLangToken = when (currentToken) { - NeoLangTokenValue.COLON -> { - moveToNextChar(eofThrow = true) - NeoLangToken(NeoLangTokenType.COLON, currentToken) - } - NeoLangTokenValue.BRACKET_START -> { - moveToNextChar(eofThrow = true) - NeoLangToken(NeoLangTokenType.BRACKET_START, currentToken) - } - NeoLangTokenValue.BRACKET_END -> { - moveToNextChar() - NeoLangToken(NeoLangTokenType.BRACKET_END, currentToken) - } - NeoLangTokenValue.ARRAY_START -> { - moveToNextChar() - NeoLangToken(NeoLangTokenType.ARRAY_START, currentToken) - } - NeoLangTokenValue.ARRAY_END -> { - moveToNextChar() - NeoLangToken(NeoLangTokenType.ARRAY_END, currentToken) - } - NeoLangTokenValue.COMMA -> { - moveToNextChar(eofThrow = true) - NeoLangToken(NeoLangTokenType.COMMA, currentToken) - } - NeoLangTokenValue.QUOTE -> { - NeoLangToken(NeoLangTokenType.STRING, NeoLangTokenValue.wrap(getNextTokenAsString())) - } - else -> { - if (currentChar.isNumber()) { - NeoLangToken(NeoLangTokenType.NUMBER, NeoLangTokenValue.wrap(getNextTokenAsNumber())) - } else if (isIdentifier(currentChar, true)) { - NeoLangToken(NeoLangTokenType.ID, NeoLangTokenValue.wrap(getNextTokenAsId())) - } else { - throw InvalidTokenException("Unexpected character near line $lineNumber: $currentChar") - } - } - } - - token.lineNumber = lineNumber - return token - } - - private fun getNextTokenAsString(): String { - // Skip start quote - // and a single quote is now allowed - moveToNextChar(eofThrow = true) - val builder = StringBuilder() - - var loop = true - while (loop && currentChar != NeoLangTokenValue.QUOTE.value.asString()[0]) { - // NeoLang does not support escaped char + var loop = true + while (loop && currentChar != NeoLangTokenValue.QUOTE.value.asString()[0]) { + // NeoLang does not support escaped char // if (currentChar == '\\') { // builder.append('\\') // moveToNextChar(eofThrow = true) // } - builder.append(currentChar) - loop = moveToNextChar() - } - - // Skip end quote - moveToNextChar() - return builder.toString() + builder.append(currentChar) + loop = moveToNextChar() } - private fun getNextTokenAsNumber(): String { - var numberValue: Double = (currentChar.toInt() - '0'.toInt()).toDouble() + // Skip end quote + moveToNextChar() + return builder.toString() + } - // Four types of numbers are supported: - // Dec(123) Hex(0x123) Oct(017) Bin(0b11) + private fun getNextTokenAsNumber(): String { + var numberValue: Double = (currentChar.toInt() - '0'.toInt()).toDouble() - // Dec - if (numberValue > 0) { - numberValue = getNextDecimalNumber(numberValue) - } else { - // is 0 - if (!moveToNextChar()) { - return numberValue.toString() - } - - // Hex - if (currentChar == 'x' || currentChar == 'X') { - numberValue = getNextHexNumber(numberValue) - } else if (currentChar == 'b' || currentChar == 'B') { - numberValue = getNextBinaryNumber(numberValue) - } else { - numberValue = getNextOctalNumber(numberValue) - } - } + // Four types of numbers are supported: + // Dec(123) Hex(0x123) Oct(017) Bin(0b11) + // Dec + if (numberValue > 0) { + numberValue = getNextDecimalNumber(numberValue) + } else { + // is 0 + if (!moveToNextChar()) { return numberValue.toString() + } + + // Hex + if (currentChar == 'x' || currentChar == 'X') { + numberValue = getNextHexNumber(numberValue) + } else if (currentChar == 'b' || currentChar == 'B') { + numberValue = getNextBinaryNumber(numberValue) + } else { + numberValue = getNextOctalNumber(numberValue) + } } - private fun getNextOctalNumber(numberValue: Double): Double { - var value: Double = numberValue - var loop = true - while (loop && currentChar in ('0'..'7')) { - value = value * 8 + currentChar.toNumber() - loop = moveToNextChar() + return numberValue.toString() + } + + private fun getNextOctalNumber(numberValue: Double): Double { + var value: Double = numberValue + var loop = true + while (loop && currentChar in ('0'..'7')) { + value = value * 8 + currentChar.toNumber() + loop = moveToNextChar() + } + return value + } + + private fun getNextBinaryNumber(numberValue: Double): Double { + var value = numberValue + var loop = moveToNextChar() // skip 'b' or 'B' + while (loop && currentChar in ('0'..'1')) { + value += value * 2 + currentChar.toNumber() + loop = moveToNextChar() + } + return value + } + + private fun getNextHexNumber(numberValue: Double): Double { + var value = numberValue + var loop = moveToNextChar() // skip 'x' or 'X' + while (loop && (currentChar.isHexNumber())) { + value *= 16 + +(currentChar.toInt().and(15)) + +if (currentChar >= 'A') 9 else 0 + loop = moveToNextChar() + } + return value + } + + private fun getNextDecimalNumber(numberValue: Double): Double { + var floatPointMeet = false + var floatPart: Double = 0.0 + var floatNumberCounter = 1 + var value = numberValue + + var loop = moveToNextChar() + while (loop) { + if (currentChar.isNumber()) { + if (floatPointMeet) { + floatPart = floatPart * 10 + currentChar.toNumber() + floatNumberCounter *= 10 + } else { + value = value * 10 + currentChar.toNumber() } - return value - } + loop = moveToNextChar() - private fun getNextBinaryNumber(numberValue: Double): Double { - var value = numberValue - var loop = moveToNextChar() // skip 'b' or 'B' - while (loop && currentChar in ('0'..'1')) { - value += value * 2 + currentChar.toNumber() - loop = moveToNextChar() + } else if (currentChar == '.') { + floatPointMeet = true + loop = moveToNextChar() + } else { + break + } + } + return value + floatPart / floatNumberCounter + } + + private fun getNextTokenAsId(): String { + return buildString { + while (isIdentifier(currentChar, false)) { + append(currentChar) + if (!moveToNextChar()) { + break } - return value + } } + } - private fun getNextHexNumber(numberValue: Double): Double { - var value = numberValue - var loop = moveToNextChar() // skip 'x' or 'X' - while (loop && (currentChar.isHexNumber())) { - value *= 16 - + (currentChar.toInt().and(15)) - + if (currentChar >= 'A') 9 else 0 - loop = moveToNextChar() - } - return value - } + private fun isIdentifier(tokenChar: Char, isFirstChar: Boolean): Boolean { + val isId = (tokenChar in 'a'..'z') + || (tokenChar in 'A'..'Z') + || ("_-#$".contains(tokenChar)) + return if (isFirstChar) isId else (isId || (tokenChar in '0'..'9')) + } - private fun getNextDecimalNumber(numberValue: Double): Double { - var floatPointMeet = false - var floatPart: Double = 0.0 - var floatNumberCounter = 1 - var value = numberValue + private fun Char.toNumber(): Int { + return if (isNumber()) { + this.toInt() - '0'.toInt() + } else 0 + } - var loop = moveToNextChar() - while (loop) { - if (currentChar.isNumber()) { - if (floatPointMeet) { - floatPart = floatPart * 10 + currentChar.toNumber() - floatNumberCounter *= 10 - } else { - value = value * 10 + currentChar.toNumber() - } - loop = moveToNextChar() + private fun Char.isNumber(): Boolean { + return this in ('0'..'9') + } - } else if (currentChar == '.') { - floatPointMeet = true - loop = moveToNextChar() - } else { - break - } - } - return value + floatPart / floatNumberCounter - } - - private fun getNextTokenAsId(): String { - return buildString { - while (isIdentifier(currentChar, false)) { - append(currentChar) - if (!moveToNextChar()) { - break - } - } - } - } - - private fun isIdentifier(tokenChar: Char, isFirstChar: Boolean): Boolean { - val isId = (tokenChar in 'a'..'z') - || (tokenChar in 'A'..'Z') - || ("_-#$".contains(tokenChar)) - return if (isFirstChar) isId else (isId || (tokenChar in '0'..'9')) - } - - private fun Char.toNumber(): Int { - return if (isNumber()) { - this.toInt() - '0'.toInt() - } else 0 - } - - private fun Char.isNumber(): Boolean { - return this in ('0'..'9') - } - - private fun Char.isHexNumber(): Boolean { - return this.isNumber() - || this in ('a'..'f') - || this in ('A'..'F') - } + private fun Char.isHexNumber(): Boolean { + return this.isNumber() + || this in ('a'..'f') + || this in ('A'..'F') + } } diff --git a/NeoLang/src/main/java/io/neolang/parser/NeoLangParser.kt b/NeoLang/src/main/java/io/neolang/parser/NeoLangParser.kt index 8679182..7a06b33 100755 --- a/NeoLang/src/main/java/io/neolang/parser/NeoLangParser.kt +++ b/NeoLang/src/main/java/io/neolang/parser/NeoLangParser.kt @@ -10,196 +10,200 @@ import io.neolang.ast.node.* * @author kiva */ class NeoLangParser { - private val lexer = NeoLangLexer() - private var tokens = mutableListOf() - private var currentPosition: Int = 0 - private var currentToken: NeoLangToken? = null + private val lexer = NeoLangLexer() + private var tokens = mutableListOf() + private var currentPosition: Int = 0 + private var currentToken: NeoLangToken? = null - fun setInputSource(programCode: String?) { - lexer.setInputSource(programCode) + fun setInputSource(programCode: String?) { + lexer.setInputSource(programCode) + } + + fun parse(): NeoLangAst { + return updateParserStatus(lexer.lex()) ?: throw ParseException("AST is null") + } + + private fun updateParserStatus(tokens: List): NeoLangAst? { + if (tokens.isEmpty()) { + // Allow empty program + return NeoLangProgramNode.emptyNode() } - fun parse(): NeoLangAst { - return updateParserStatus(lexer.lex()) ?: throw ParseException("AST is null") + this.tokens.clear() + this.tokens.addAll(tokens) + currentPosition = 0 + currentToken = tokens[currentPosition] + return program() + } + + private fun match(tokenType: NeoLangTokenType, errorThrow: Boolean = false): Boolean { + val currentToken = this.currentToken ?: throw InvalidTokenException("Unexpected token: null") + + if (currentToken.tokenType === tokenType) { + currentPosition++ + if (currentPosition >= tokens.size) { + this.currentToken = NeoLangToken(NeoLangTokenType.EOF, NeoLangTokenValue.EOF) + } else { + this.currentToken = tokens[currentPosition] + } + return true + + } else if (errorThrow) { + throw InvalidTokenException( + "Unexpected token `${currentToken.tokenValue}' typed " + + "`${currentToken.tokenType}' near line ${currentToken.lineNumber}, " + + "expected $tokenType" + ) } - private fun updateParserStatus(tokens: List): NeoLangAst? { - if (tokens.isEmpty()) { - // Allow empty program - return NeoLangProgramNode.emptyNode() + return false + } + + private fun program(): NeoLangProgramNode { + val token = currentToken + + var group = group() + if (group != null) { + val groups = mutableListOf(group) + while (token?.tokenType !== NeoLangTokenType.EOF) { + group = group() + if (group == null) { + break } - - this.tokens.clear() - this.tokens.addAll(tokens) - currentPosition = 0 - currentToken = tokens[currentPosition] - return program() + groups.add(group) + } + return NeoLangProgramNode(groups) } - private fun match(tokenType: NeoLangTokenType, errorThrow: Boolean = false): Boolean { - val currentToken = this.currentToken ?: throw InvalidTokenException("Unexpected token: null") + return NeoLangProgramNode.emptyNode() + } - if (currentToken.tokenType === tokenType) { - currentPosition++ - if (currentPosition >= tokens.size) { - this.currentToken = NeoLangToken(NeoLangTokenType.EOF, NeoLangTokenValue.EOF) - } else { - this.currentToken = tokens[currentPosition] - } - return true + /** + * @param attrName Only available when group is a attribute value + */ + private fun group(): NeoLangGroupNode? { + val token = currentToken ?: throw InvalidTokenException("Unexpected token: null") - } else if (errorThrow) { - throw InvalidTokenException("Unexpected token `${currentToken.tokenValue}' typed " + - "`${currentToken.tokenType}' near line ${currentToken.lineNumber}, " + - "expected $tokenType") + var attr = attribute() + if (attr != null) { + val attributes = mutableListOf(attr) + + while (token.tokenType !== NeoLangTokenType.EOF + && token.tokenType !== NeoLangTokenType.BRACKET_END + && token.tokenType !== NeoLangTokenType.ARRAY_END + ) { + attr = attribute() + if (attr == null) { + break } - - return false + attributes.add(attr) + } + return NeoLangGroupNode(attributes.toTypedArray()) } - private fun program(): NeoLangProgramNode { - val token = currentToken + return null + } - var group = group() - if (group != null) { - val groups = mutableListOf(group) - while (token?.tokenType !== NeoLangTokenType.EOF) { - group = group() - if (group == null) { - break - } - groups.add(group) - } - return NeoLangProgramNode(groups) + private fun attribute(): NeoLangAttributeNode? { + val token = currentToken ?: throw InvalidTokenException("Unexpected token: null") + if (match(NeoLangTokenType.ID)) { + match(NeoLangTokenType.COLON, errorThrow = true) + + val attrName = NeoLangStringNode(token) + + val block = block(attrName) ?: NeoLangBlockNode.emptyNode() + return NeoLangAttributeNode(attrName, block) + } + return null + } + + private fun array(arrayName: NeoLangStringNode): NeoLangArrayNode? { + val token = currentToken ?: throw InvalidTokenException("Unexpected token: null") + + // TODO: Multiple Array + var block = blockNonArrayElement(arrayName) + var index = 0 + + if (block != null) { + val elements = mutableListOf(NeoLangArrayNode.Companion.ArrayElement(index++, block)) + + if (match(NeoLangTokenType.COMMA)) { + // More than one elements + while (token.tokenType !== NeoLangTokenType.EOF + && token.tokenType !== NeoLangTokenType.ARRAY_END + ) { + block = blockNonArrayElement(arrayName) + if (block == null) { + break + } + elements.add(NeoLangArrayNode.Companion.ArrayElement(index++, block)) + + // Meet the last element + if (!match(NeoLangTokenType.COMMA)) { + break + } } + } - return NeoLangProgramNode.emptyNode() + return NeoLangArrayNode(arrayName, elements.toTypedArray()) } - /** - * @param attrName Only available when group is a attribute value - */ - private fun group(): NeoLangGroupNode? { - val token = currentToken ?: throw InvalidTokenException("Unexpected token: null") + return null + } - var attr = attribute() - if (attr != null) { - val attributes = mutableListOf(attr) - while (token.tokenType !== NeoLangTokenType.EOF - && token.tokenType !== NeoLangTokenType.BRACKET_END - && token.tokenType !== NeoLangTokenType.ARRAY_END) { - attr = attribute() - if (attr == null) { - break - } - attributes.add(attr) - } - return NeoLangGroupNode(attributes.toTypedArray()) - } - - return null + /** + * @param attrName The block holder's name + */ + private fun block(attrName: NeoLangStringNode): NeoLangBlockNode? { + val block = blockNonArrayElement(attrName) + if (block != null) { + return block } - private fun attribute(): NeoLangAttributeNode? { - val token = currentToken ?: throw InvalidTokenException("Unexpected token: null") - if (match(NeoLangTokenType.ID)) { - match(NeoLangTokenType.COLON, errorThrow = true) + val token = currentToken ?: throw InvalidTokenException("Unexpected token: null") + when (token.tokenType) { + NeoLangTokenType.ARRAY_START -> { + match(NeoLangTokenType.ARRAY_START, errorThrow = true) + val array = array(attrName) + match(NeoLangTokenType.ARRAY_END, errorThrow = true) - val attrName = NeoLangStringNode(token) + // Allow empty arrays + return if (array != null) NeoLangBlockNode(array) else NeoLangBlockNode.emptyNode() + } - val block = block(attrName) ?: NeoLangBlockNode.emptyNode() - return NeoLangAttributeNode(attrName, block) - } - return null + else -> throw InvalidTokenException("Unexpected token `${token.tokenValue}' typed `${token.tokenType}' for block") } + } - private fun array(arrayName: NeoLangStringNode): NeoLangArrayNode? { - val token = currentToken ?: throw InvalidTokenException("Unexpected token: null") + /** + * @param attrName Only available when group is a attribute value + */ + private fun blockNonArrayElement(attrName: NeoLangStringNode?): NeoLangBlockNode? { + val token = currentToken ?: throw InvalidTokenException("Unexpected token: null") - // TODO: Multiple Array - var block = blockNonArrayElement(arrayName) - var index = 0 + return when (token.tokenType) { + NeoLangTokenType.NUMBER -> { + match(NeoLangTokenType.NUMBER, errorThrow = true) + return NeoLangBlockNode(NeoLangNumberNode(token)) + } + NeoLangTokenType.ID -> { + match(NeoLangTokenType.ID, errorThrow = true) + return NeoLangBlockNode(NeoLangStringNode(token)) + } + NeoLangTokenType.STRING -> { + match(NeoLangTokenType.STRING, errorThrow = true) + return NeoLangBlockNode(NeoLangStringNode(token)) + } + NeoLangTokenType.BRACKET_START -> { + match(NeoLangTokenType.BRACKET_START, errorThrow = true) + val group = group() + match(NeoLangTokenType.BRACKET_END, errorThrow = true) - if (block != null) { - val elements = mutableListOf(NeoLangArrayNode.Companion.ArrayElement(index++, block)) - - if (match(NeoLangTokenType.COMMA)) { - // More than one elements - while (token.tokenType !== NeoLangTokenType.EOF - && token.tokenType !== NeoLangTokenType.ARRAY_END) { - block = blockNonArrayElement(arrayName) - if (block == null) { - break - } - elements.add(NeoLangArrayNode.Companion.ArrayElement(index++, block)) - - // Meet the last element - if (!match(NeoLangTokenType.COMMA)) { - break - } - } - } - - return NeoLangArrayNode(arrayName, elements.toTypedArray()) - } - - return null - } - - - /** - * @param attrName The block holder's name - */ - private fun block(attrName: NeoLangStringNode): NeoLangBlockNode? { - val block = blockNonArrayElement(attrName) - if (block != null) { - return block - } - - val token = currentToken ?: throw InvalidTokenException("Unexpected token: null") - when (token.tokenType) { - NeoLangTokenType.ARRAY_START -> { - match(NeoLangTokenType.ARRAY_START, errorThrow = true) - val array = array(attrName) - match(NeoLangTokenType.ARRAY_END, errorThrow = true) - - // Allow empty arrays - return if (array != null) NeoLangBlockNode(array) else NeoLangBlockNode.emptyNode() - } - - else -> throw InvalidTokenException("Unexpected token `${token.tokenValue}' typed `${token.tokenType}' for block") - } - } - - /** - * @param attrName Only available when group is a attribute value - */ - private fun blockNonArrayElement(attrName: NeoLangStringNode?): NeoLangBlockNode? { - val token = currentToken ?: throw InvalidTokenException("Unexpected token: null") - - return when (token.tokenType) { - NeoLangTokenType.NUMBER -> { - match(NeoLangTokenType.NUMBER, errorThrow = true) - return NeoLangBlockNode(NeoLangNumberNode(token)) - } - NeoLangTokenType.ID -> { - match(NeoLangTokenType.ID, errorThrow = true) - return NeoLangBlockNode(NeoLangStringNode(token)) - } - NeoLangTokenType.STRING -> { - match(NeoLangTokenType.STRING, errorThrow = true) - return NeoLangBlockNode(NeoLangStringNode(token)) - } - NeoLangTokenType.BRACKET_START -> { - match(NeoLangTokenType.BRACKET_START, errorThrow = true) - val group = group() - match(NeoLangTokenType.BRACKET_END, errorThrow = true) - - // Allow empty blocks - return if (group != null) NeoLangBlockNode(group) else NeoLangBlockNode.emptyNode() - } - else -> null - } + // Allow empty blocks + return if (group != null) NeoLangBlockNode(group) else NeoLangBlockNode.emptyNode() + } + else -> null } + } } diff --git a/NeoLang/src/main/java/io/neolang/runtime/context/NeoLangContext.kt b/NeoLang/src/main/java/io/neolang/runtime/context/NeoLangContext.kt index 404ac36..f688c46 100644 --- a/NeoLang/src/main/java/io/neolang/runtime/context/NeoLangContext.kt +++ b/NeoLang/src/main/java/io/neolang/runtime/context/NeoLangContext.kt @@ -6,34 +6,34 @@ import io.neolang.runtime.type.NeoLangValue * @author kiva */ class NeoLangContext(val contextName: String) { - companion object { - private val emptyContext = NeoLangContext("") - } + companion object { + private val emptyContext = NeoLangContext("") + } - private val attributes = mutableMapOf() - val children = mutableListOf() - var parent: NeoLangContext? = null + private val attributes = mutableMapOf() + val children = mutableListOf() + var parent: NeoLangContext? = null - fun defineAttribute(attributeName: String, attributeValue: NeoLangValue): NeoLangContext { - attributes[attributeName] = attributeValue - return this - } + fun defineAttribute(attributeName: String, attributeValue: NeoLangValue): NeoLangContext { + attributes[attributeName] = attributeValue + return this + } - fun getAttribute(attributeName: String): NeoLangValue { - return attributes[attributeName] ?: parent?.getAttribute(attributeName) ?: NeoLangValue.UNDEFINED - } + fun getAttribute(attributeName: String): NeoLangValue { + return attributes[attributeName] ?: parent?.getAttribute(attributeName) ?: NeoLangValue.UNDEFINED + } - fun getChild(contextName: String): NeoLangContext { - var found: NeoLangContext? = null - children.forEach { - if (it.contextName == contextName) { - found = it - } - } - return found ?: emptyContext + fun getChild(contextName: String): NeoLangContext { + var found: NeoLangContext? = null + children.forEach { + if (it.contextName == contextName) { + found = it + } } + return found ?: emptyContext + } - fun getAttributes(): Map { - return attributes - } + fun getAttributes(): Map { + return attributes + } } diff --git a/NeoLang/src/main/java/io/neolang/runtime/type/NeoLangArray.kt b/NeoLang/src/main/java/io/neolang/runtime/type/NeoLangArray.kt index 8411d02..eeaecd3 100644 --- a/NeoLang/src/main/java/io/neolang/runtime/type/NeoLangArray.kt +++ b/NeoLang/src/main/java/io/neolang/runtime/type/NeoLangArray.kt @@ -5,55 +5,58 @@ import io.neolang.runtime.context.NeoLangContext /** * @author kiva */ -class NeoLangArray private constructor(val elements: List, override val size: Int = elements.size) : Collection { - companion object { - internal class PrimaryElement(val primaryValue: NeoLangValue) : NeoLangArrayElement() { - override fun eval(): NeoLangValue { - return primaryValue - } - } - - internal class BlockElement(val blockContext: NeoLangContext) : NeoLangArrayElement() { - override fun eval(key: String): NeoLangValue { - return blockContext.getAttribute(key) - } - - override fun isBlock(): Boolean { - return true - } - } - - fun createFromContext(context: NeoLangContext): NeoLangArray { - val elements = mutableListOf() - context.getAttributes().entries.forEach { - val index = it.key.toInt() - elements.add(index, PrimaryElement(it.value)) - } - context.children.forEach { - val index = it.contextName.toInt() - elements.add(index, BlockElement(it)) - } - return NeoLangArray(elements) - } +class NeoLangArray private constructor( + val elements: List, + override val size: Int = elements.size +) : Collection { + companion object { + internal class PrimaryElement(val primaryValue: NeoLangValue) : NeoLangArrayElement() { + override fun eval(): NeoLangValue { + return primaryValue + } } - operator fun get(index: Int): NeoLangArrayElement { - return elements[index] + internal class BlockElement(val blockContext: NeoLangContext) : NeoLangArrayElement() { + override fun eval(key: String): NeoLangValue { + return blockContext.getAttribute(key) + } + + override fun isBlock(): Boolean { + return true + } } - override fun contains(element: NeoLangArrayElement): Boolean { - return elements.contains(element) + fun createFromContext(context: NeoLangContext): NeoLangArray { + val elements = mutableListOf() + context.getAttributes().entries.forEach { + val index = it.key.toInt() + elements.add(index, PrimaryElement(it.value)) + } + context.children.forEach { + val index = it.contextName.toInt() + elements.add(index, BlockElement(it)) + } + return NeoLangArray(elements) } + } - override fun containsAll(elements: Collection): Boolean { - return this.elements.containsAll(elements) - } + operator fun get(index: Int): NeoLangArrayElement { + return elements[index] + } - override fun isEmpty(): Boolean { - return size == 0 - } + override fun contains(element: NeoLangArrayElement): Boolean { + return elements.contains(element) + } - override fun iterator(): Iterator { - return elements.iterator() - } + override fun containsAll(elements: Collection): Boolean { + return this.elements.containsAll(elements) + } + + override fun isEmpty(): Boolean { + return size == 0 + } + + override fun iterator(): Iterator { + return elements.iterator() + } } \ No newline at end of file diff --git a/NeoLang/src/main/java/io/neolang/runtime/type/NeoLangArrayElement.kt b/NeoLang/src/main/java/io/neolang/runtime/type/NeoLangArrayElement.kt index 6395255..70e9a18 100644 --- a/NeoLang/src/main/java/io/neolang/runtime/type/NeoLangArrayElement.kt +++ b/NeoLang/src/main/java/io/neolang/runtime/type/NeoLangArrayElement.kt @@ -4,15 +4,15 @@ package io.neolang.runtime.type * @author kiva */ open class NeoLangArrayElement { - open fun eval(): NeoLangValue { - return NeoLangValue.UNDEFINED - } + open fun eval(): NeoLangValue { + return NeoLangValue.UNDEFINED + } - open fun eval(key: String): NeoLangValue { - return NeoLangValue.UNDEFINED - } + open fun eval(key: String): NeoLangValue { + return NeoLangValue.UNDEFINED + } - open fun isBlock(): Boolean { - return false - } + open fun isBlock(): Boolean { + return false + } } \ No newline at end of file diff --git a/NeoLang/src/main/java/io/neolang/runtime/type/NeoLangValue.kt b/NeoLang/src/main/java/io/neolang/runtime/type/NeoLangValue.kt index be9190f..fd406e4 100644 --- a/NeoLang/src/main/java/io/neolang/runtime/type/NeoLangValue.kt +++ b/NeoLang/src/main/java/io/neolang/runtime/type/NeoLangValue.kt @@ -4,27 +4,27 @@ package io.neolang.runtime.type * @author kiva */ class NeoLangValue(private val rawValue: Any) { - fun asString(): String { - return rawValue.toString() + fun asString(): String { + return rawValue.toString() + } + + fun asNumber(): Double { + if (rawValue is Array<*>) { + return 0.0 } - fun asNumber(): Double { - if (rawValue is Array<*>) { - return 0.0 - } - - try { - return rawValue.toString().toDouble() - } catch (e: Throwable) { - return 0.0 - } + try { + return rawValue.toString().toDouble() + } catch (e: Throwable) { + return 0.0 } + } - fun isValid(): Boolean { - return this != UNDEFINED - } + fun isValid(): Boolean { + return this != UNDEFINED + } - companion object { - val UNDEFINED = NeoLangValue("") - } + companion object { + val UNDEFINED = NeoLangValue("") + } } \ No newline at end of file diff --git a/NeoLang/src/main/java/io/neolang/visitor/ConfigVisitor.kt b/NeoLang/src/main/java/io/neolang/visitor/ConfigVisitor.kt index eec1c77..46622f0 100644 --- a/NeoLang/src/main/java/io/neolang/visitor/ConfigVisitor.kt +++ b/NeoLang/src/main/java/io/neolang/visitor/ConfigVisitor.kt @@ -6,68 +6,68 @@ import io.neolang.runtime.type.NeoLangArray import io.neolang.runtime.type.NeoLangValue class ConfigVisitor : IVisitorCallback { - private var rootContext: NeoLangContext? = null - private var currentContext: NeoLangContext? = null + private var rootContext: NeoLangContext? = null + private var currentContext: NeoLangContext? = null - fun getRootContext(): NeoLangContext { - return rootContext!! - } + fun getRootContext(): NeoLangContext { + return rootContext!! + } - fun getContext(contextPath: Array): NeoLangContext { - var context = getCurrentContext() - contextPath.forEach { - context = context.getChild(it) - } - return context + fun getContext(contextPath: Array): NeoLangContext { + var context = getCurrentContext() + contextPath.forEach { + context = context.getChild(it) } + return context + } - fun getAttribute(contextPath: Array, attrName: String): NeoLangValue { - return getContext(contextPath).getAttribute(attrName) - } + fun getAttribute(contextPath: Array, attrName: String): NeoLangValue { + return getContext(contextPath).getAttribute(attrName) + } - fun getArray(contextPath: Array, arrayName: String): NeoLangArray { - // We use NeoLangContext as arrays and array elements now - return NeoLangArray.createFromContext(getContext(contextPath).getChild(arrayName)) - } + fun getArray(contextPath: Array, arrayName: String): NeoLangArray { + // We use NeoLangContext as arrays and array elements now + return NeoLangArray.createFromContext(getContext(contextPath).getChild(arrayName)) + } - fun getStringValue(path: Array, name: String): String? { - val value = this.getAttribute(path, name) - return if (value.isValid()) value.asString() else null - } + fun getStringValue(path: Array, name: String): String? { + val value = this.getAttribute(path, name) + return if (value.isValid()) value.asString() else null + } - fun getBooleanValue(path: Array, name: String): Boolean? { - val value = this.getAttribute(path, name) - return if (value.isValid()) value.asString() == "true" else null - } + fun getBooleanValue(path: Array, name: String): Boolean? { + val value = this.getAttribute(path, name) + return if (value.isValid()) value.asString() == "true" else null + } - override fun onStart() { - currentContext = NeoLangContext("global") - rootContext = currentContext - } + override fun onStart() { + currentContext = NeoLangContext("global") + rootContext = currentContext + } - override fun onFinish() { - var context = currentContext - while (context != null && context.parent != null) { - context = context.parent - } - this.currentContext = context + override fun onFinish() { + var context = currentContext + while (context != null && context.parent != null) { + context = context.parent } + this.currentContext = context + } - override fun onEnterContext(contextName: String) { - val newContext = NeoLangContext(contextName) - newContext.parent = currentContext - currentContext!!.children.add(newContext) - currentContext = newContext - } + override fun onEnterContext(contextName: String) { + val newContext = NeoLangContext(contextName) + newContext.parent = currentContext + currentContext!!.children.add(newContext) + currentContext = newContext + } - override fun onExitContext() { - val context = currentContext - if (context?.parent != null) { - this.currentContext = context.parent - } + override fun onExitContext() { + val context = currentContext + if (context?.parent != null) { + this.currentContext = context.parent } + } - override fun getCurrentContext(): NeoLangContext { - return currentContext!! - } + override fun getCurrentContext(): NeoLangContext { + return currentContext!! + } } \ No newline at end of file diff --git a/NeoLang/src/main/java/io/neolang/visitor/DisplayProcessVisitor.kt b/NeoLang/src/main/java/io/neolang/visitor/DisplayProcessVisitor.kt index 8570748..c8e854b 100644 --- a/NeoLang/src/main/java/io/neolang/visitor/DisplayProcessVisitor.kt +++ b/NeoLang/src/main/java/io/neolang/visitor/DisplayProcessVisitor.kt @@ -8,35 +8,35 @@ import java.util.* * @author kiva */ class DisplayProcessVisitor : IVisitorCallbackAdapter() { - private val contextStack = Stack() + private val contextStack = Stack() - override fun onStart() { - println(">>> Start") - onEnterContext("global") - } + override fun onStart() { + println(">>> Start") + onEnterContext("global") + } - override fun onFinish() { - while (contextStack.isNotEmpty()) { - onExitContext() - } - println(">>> Finish") + override fun onFinish() { + while (contextStack.isNotEmpty()) { + onExitContext() } + println(">>> Finish") + } - override fun onEnterContext(contextName: String) { - val context = NeoLangContext(contextName) - contextStack.push(context) - println(">>> Entering Context `$contextName'") - } + override fun onEnterContext(contextName: String) { + val context = NeoLangContext(contextName) + contextStack.push(context) + println(">>> Entering Context `$contextName'") + } - override fun onExitContext() { - val context = contextStack.pop() - println(">>> Exiting & Dumping Context ${context.contextName}") - context.getAttributes().entries.forEach { - println(" > [${it.key}]: ${it.value.asString()}") - } + override fun onExitContext() { + val context = contextStack.pop() + println(">>> Exiting & Dumping Context ${context.contextName}") + context.getAttributes().entries.forEach { + println(" > [${it.key}]: ${it.value.asString()}") } + } - override fun getCurrentContext(): NeoLangContext { - return contextStack.peek() - } + override fun getCurrentContext(): NeoLangContext { + return contextStack.peek() + } } diff --git a/NeoLang/src/test/java/io/neolang/NeoLangTest.kt b/NeoLang/src/test/java/io/neolang/NeoLangTest.kt index 2732603..6a2151e 100644 --- a/NeoLang/src/test/java/io/neolang/NeoLangTest.kt +++ b/NeoLang/src/test/java/io/neolang/NeoLangTest.kt @@ -9,9 +9,9 @@ import org.junit.Test * @see [Testing documentation](http://d.android.com/tools/testing) */ class NeoLangTest { - @Test - fun arrayTest() { - Main.main(arrayOf("NeoLang/example/extra-key.nl")) - } + @Test + fun arrayTest() { + Main.main(arrayOf("NeoLang/example/extra-key.nl")) + } } diff --git a/NeoTermBridge/build.gradle b/NeoTermBridge/build.gradle index 974ede2..cc06b6d 100644 --- a/NeoTermBridge/build.gradle +++ b/NeoTermBridge/build.gradle @@ -4,28 +4,28 @@ def libraryVersionCode = 1 def libraryVersionName = "1.0" android { - compileSdkVersion rootProject.ext.android.COMPILE_SDK_VERSION + compileSdkVersion rootProject.ext.android.COMPILE_SDK_VERSION - defaultConfig { - minSdkVersion rootProject.ext.android.MIN_SDK_VERSION - targetSdkVersion rootProject.ext.android.TARGET_SDK_VERSION - versionCode libraryVersionCode - versionName libraryVersionName - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' - } + defaultConfig { + minSdkVersion rootProject.ext.android.MIN_SDK_VERSION + targetSdkVersion rootProject.ext.android.TARGET_SDK_VERSION + versionCode libraryVersionCode + versionName libraryVersionName + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + } - buildTypes { - release { - minifyEnabled false - } + buildTypes { + release { + minifyEnabled false } + } } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.2.0' - androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { - exclude group: 'com.android.support', module: 'support-annotations' - }) - testImplementation rootProject.ext.deps["junit"] + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.2.0' + androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + testImplementation rootProject.ext.deps["junit"] } diff --git a/NeoTermBridge/src/main/AndroidManifest.xml b/NeoTermBridge/src/main/AndroidManifest.xml index 8378b0b..77cf7d7 100644 --- a/NeoTermBridge/src/main/AndroidManifest.xml +++ b/NeoTermBridge/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/NeoTermBridge/src/main/java/io/neoterm/bridge/Bridge.java b/NeoTermBridge/src/main/java/io/neoterm/bridge/Bridge.java index 0a2bdaa..4687134 100644 --- a/NeoTermBridge/src/main/java/io/neoterm/bridge/Bridge.java +++ b/NeoTermBridge/src/main/java/io/neoterm/bridge/Bridge.java @@ -9,52 +9,52 @@ import java.util.Objects; * @author kiva */ public class Bridge { - public static final String ACTION_EXECUTE = "neoterm.action.remote.execute"; - public static final String ACTION_SILENT_RUN = "neoterm.action.remote.silent-run"; - public static final String EXTRA_COMMAND = "neoterm.extra.remote.execute.command"; - public static final String EXTRA_SESSION_ID = "neoterm.extra.remote.execute.session"; - public static final String EXTRA_FOREGROUND = "neoterm.extra.remote.execute.foreground"; - private static final String NEOTERM_PACKAGE = "io.neoterm"; - private static final String NEOTERM_REMOTE_INTERFACE = "io.neoterm.ui.term.NeoTermRemoteInterface"; - private static final ComponentName NEOTERM_COMPONENT = new ComponentName(NEOTERM_PACKAGE, NEOTERM_REMOTE_INTERFACE); + public static final String ACTION_EXECUTE = "neoterm.action.remote.execute"; + public static final String ACTION_SILENT_RUN = "neoterm.action.remote.silent-run"; + public static final String EXTRA_COMMAND = "neoterm.extra.remote.execute.command"; + public static final String EXTRA_SESSION_ID = "neoterm.extra.remote.execute.session"; + public static final String EXTRA_FOREGROUND = "neoterm.extra.remote.execute.foreground"; + private static final String NEOTERM_PACKAGE = "io.neoterm"; + private static final String NEOTERM_REMOTE_INTERFACE = "io.neoterm.ui.term.NeoTermRemoteInterface"; + private static final ComponentName NEOTERM_COMPONENT = new ComponentName(NEOTERM_PACKAGE, NEOTERM_REMOTE_INTERFACE); - private Bridge() throws IllegalAccessException { - throw new IllegalAccessException(); - } - - public static Intent createExecuteIntent(SessionId sessionId, - String command, - boolean foreground) { - Objects.requireNonNull(command, "command"); - Objects.requireNonNull(sessionId, "session id"); - - Intent intent = new Intent(ACTION_EXECUTE); - intent.setComponent(NEOTERM_COMPONENT); - intent.putExtra(EXTRA_COMMAND, command); - intent.putExtra(EXTRA_SESSION_ID, sessionId.getSessionId()); - intent.putExtra(EXTRA_FOREGROUND, foreground); - return intent; - } - - public static Intent createExecuteIntent(SessionId sessionId, String command) { - return createExecuteIntent(sessionId, command, true); - } - - public static Intent createExecuteIntent(String command) { - return createExecuteIntent(SessionId.NEW_SESSION, command); - } - - public static Intent createExecuteIntent(String command, boolean foreground) { - return createExecuteIntent(SessionId.NEW_SESSION, command, foreground); - } - - public static SessionId parseResult(Intent data) { - Objects.requireNonNull(data, "data"); - - if (data.hasExtra(EXTRA_SESSION_ID)) { - String handle = data.getStringExtra(EXTRA_SESSION_ID); - return SessionId.of(handle); - } - return null; + private Bridge() throws IllegalAccessException { + throw new IllegalAccessException(); + } + + public static Intent createExecuteIntent(SessionId sessionId, + String command, + boolean foreground) { + Objects.requireNonNull(command, "command"); + Objects.requireNonNull(sessionId, "session id"); + + Intent intent = new Intent(ACTION_EXECUTE); + intent.setComponent(NEOTERM_COMPONENT); + intent.putExtra(EXTRA_COMMAND, command); + intent.putExtra(EXTRA_SESSION_ID, sessionId.getSessionId()); + intent.putExtra(EXTRA_FOREGROUND, foreground); + return intent; + } + + public static Intent createExecuteIntent(SessionId sessionId, String command) { + return createExecuteIntent(sessionId, command, true); + } + + public static Intent createExecuteIntent(String command) { + return createExecuteIntent(SessionId.NEW_SESSION, command); + } + + public static Intent createExecuteIntent(String command, boolean foreground) { + return createExecuteIntent(SessionId.NEW_SESSION, command, foreground); + } + + public static SessionId parseResult(Intent data) { + Objects.requireNonNull(data, "data"); + + if (data.hasExtra(EXTRA_SESSION_ID)) { + String handle = data.getStringExtra(EXTRA_SESSION_ID); + return SessionId.of(handle); } + return null; + } } diff --git a/NeoTermBridge/src/main/java/io/neoterm/bridge/SessionId.java b/NeoTermBridge/src/main/java/io/neoterm/bridge/SessionId.java index ea3d11f..7ebfe13 100644 --- a/NeoTermBridge/src/main/java/io/neoterm/bridge/SessionId.java +++ b/NeoTermBridge/src/main/java/io/neoterm/bridge/SessionId.java @@ -6,45 +6,45 @@ import java.util.Objects; * @author kiva */ public class SessionId { - /** - * Created a new session. - */ - public static final SessionId NEW_SESSION = SessionId.of("new"); + /** + * Created a new session. + */ + public static final SessionId NEW_SESSION = SessionId.of("new"); - /** - * Presents current session in NeoTerm. - */ - public static final SessionId CURRENT_SESSION = SessionId.of("current"); + /** + * Presents current session in NeoTerm. + */ + public static final SessionId CURRENT_SESSION = SessionId.of("current"); - private final String sessionId; + private final String sessionId; - SessionId(String sessionId) { - this.sessionId = sessionId; - } + SessionId(String sessionId) { + this.sessionId = sessionId; + } - public String getSessionId() { - return sessionId; - } + public String getSessionId() { + return sessionId; + } - @Override - public String toString() { - return "TerminalSession { id = " + sessionId + " }"; - } + @Override + public String toString() { + return "TerminalSession { id = " + sessionId + " }"; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - SessionId sessionId1 = (SessionId) o; - return Objects.equals(sessionId, sessionId1.sessionId); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SessionId sessionId1 = (SessionId) o; + return Objects.equals(sessionId, sessionId1.sessionId); + } - @Override - public int hashCode() { - return Objects.hash(sessionId); - } + @Override + public int hashCode() { + return Objects.hash(sessionId); + } - public static SessionId of(String sessionId) { - return new SessionId(sessionId); - } + public static SessionId of(String sessionId) { + return new SessionId(sessionId); + } } diff --git a/NeoTermBridge/src/main/res/values/strings.xml b/NeoTermBridge/src/main/res/values/strings.xml index f886376..0c6bc90 100644 --- a/NeoTermBridge/src/main/res/values/strings.xml +++ b/NeoTermBridge/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - NeoTermBridge + NeoTermBridge diff --git a/NeoTermBridge/src/test/java/io/neoterm/bridge/ExampleUnitTest.java b/NeoTermBridge/src/test/java/io/neoterm/bridge/ExampleUnitTest.java index b7e7724..f4e7821 100644 --- a/NeoTermBridge/src/test/java/io/neoterm/bridge/ExampleUnitTest.java +++ b/NeoTermBridge/src/test/java/io/neoterm/bridge/ExampleUnitTest.java @@ -2,7 +2,7 @@ package io.neoterm.bridge; import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** * Example local unit test, which will execute on the development machine (host). @@ -10,8 +10,8 @@ import static org.junit.Assert.*; * @see Testing documentation */ public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } } \ No newline at end of file diff --git a/README.md b/README.md index 359fa16..e7602b3 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,18 @@ NeoTerm A modern-designed android terminal emulator for the 21st century. ### Our Pledge -Originally, NeoTerm was designed as the front end of Termux to provide some functions that Termux didn't have, but we found it very convenient. In continuous development, we discovered our goal: to be the best terminal for Android. + +Originally, NeoTerm was designed as the front end of Termux to provide some functions that Termux didn't have, but we +found it very convenient. In continuous development, we discovered our goal: to be the best terminal for Android. ### Help & Documentation + View on [GitBook](https://neoterm.gitbooks.io/neoterm-wiki/content) View on [GitHub](https://github.com/NeoTerm/NeoTerm-Wiki) ### Download + [GitHub Release Page](https://github.com/NeoTerm/NeoTerm/releases) [lzzySoft's F-Droid repo](https://apt.izzysoft.de/fdroid/index/apk/io.neoterm) (thanks to @lzzySoft) diff --git a/Xorg/build.gradle b/Xorg/build.gradle index 8eb9abb..5240a75 100644 --- a/Xorg/build.gradle +++ b/Xorg/build.gradle @@ -1,36 +1,36 @@ apply plugin: 'com.android.library' android { - compileSdkVersion rootProject.ext.android.COMPILE_SDK_VERSION + compileSdkVersion rootProject.ext.android.COMPILE_SDK_VERSION - defaultConfig { - minSdkVersion rootProject.ext.android.MIN_SDK_VERSION - targetSdkVersion rootProject.ext.android.TARGET_SDK_VERSION - versionCode 1 - versionName "1.0" - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' - } + defaultConfig { + minSdkVersion rootProject.ext.android.MIN_SDK_VERSION + targetSdkVersion rootProject.ext.android.TARGET_SDK_VERSION + versionCode 1 + versionName "1.0" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + } - buildTypes { - release { - minifyEnabled false - } + buildTypes { + release { + minifyEnabled false } + } - sourceSets { - main { - jniLibs.srcDirs = ['src/main/jniLibs'] - } + sourceSets { + main { + jniLibs.srcDirs = ['src/main/jniLibs'] } + } } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation rootProject.ext.deps["appcompat-v7"] - testImplementation rootProject.ext.deps["junit"] + implementation rootProject.ext.deps["appcompat-v7"] + testImplementation rootProject.ext.deps["junit"] - androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { - exclude group: 'com.android.support', module: 'support-annotations' - }) + androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { + exclude group: 'com.android.support', module: 'support-annotations' + }) } diff --git a/Xorg/src/main/AndroidManifest.xml b/Xorg/src/main/AndroidManifest.xml index e046fd4..99238e7 100644 --- a/Xorg/src/main/AndroidManifest.xml +++ b/Xorg/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/Xorg/src/main/java/io/neoterm/Accelerometer.java b/Xorg/src/main/java/io/neoterm/Accelerometer.java index c2fe2e4..cf27099 100644 --- a/Xorg/src/main/java/io/neoterm/Accelerometer.java +++ b/Xorg/src/main/java/io/neoterm/Accelerometer.java @@ -27,304 +27,269 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import androidx.appcompat.app.AppCompatActivity; import android.util.Log; +import androidx.appcompat.app.AppCompatActivity; import java.util.Arrays; @SuppressWarnings("JniMissingFunction") -class AccelerometerReader implements SensorEventListener -{ +class AccelerometerReader implements SensorEventListener { - private SensorManager _manager = null; - public boolean openedBySDL = false; - public static final GyroscopeListener gyro = new GyroscopeListener(); - public static final OrientationListener orientation = new OrientationListener(); + private SensorManager _manager = null; + public boolean openedBySDL = false; + public static final GyroscopeListener gyro = new GyroscopeListener(); + public static final OrientationListener orientation = new OrientationListener(); - public AccelerometerReader(Context context) - { - _manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - } - - public synchronized void stop() - { - if( _manager != null ) - { - Log.i("SDL", "libSDL: stopping accelerometer/gyroscope/orientation"); - _manager.unregisterListener(this); - _manager.unregisterListener(gyro); - _manager.unregisterListener(orientation); - } - } + public AccelerometerReader(Context context) { + _manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + } - public synchronized void start() - { - if( (Globals.UseAccelerometerAsArrowKeys || Globals.AppUsesAccelerometer) && - _manager != null && _manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null ) - { - Log.i("SDL", "libSDL: starting accelerometer"); - _manager.registerListener(this, _manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); - } - if( (Globals.AppUsesGyroscope || Globals.MoveMouseWithGyroscope) && - _manager != null && _manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null ) - { - Log.i("SDL", "libSDL: starting gyroscope"); - _manager.registerListener(gyro, _manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_GAME); - } - if( (Globals.AppUsesOrientationSensor) && _manager != null && - _manager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR) != null ) - { - Log.i("SDL", "libSDL: starting orientation sensor"); - _manager.registerListener(orientation, _manager.getDefaultSensor( - Sensor.TYPE_GAME_ROTATION_VECTOR), - SensorManager.SENSOR_DELAY_GAME); - } - } + public synchronized void stop() { + if (_manager != null) { + Log.i("SDL", "libSDL: stopping accelerometer/gyroscope/orientation"); + _manager.unregisterListener(this); + _manager.unregisterListener(gyro); + _manager.unregisterListener(orientation); + } + } - public void onSensorChanged(SensorEvent event) - { - if( Globals.HorizontalOrientation ) - { - if( gyro.invertedOrientation ) - nativeAccelerometer(-event.values[1], event.values[0], event.values[2]); - else - nativeAccelerometer(event.values[1], -event.values[0], event.values[2]); - } - else - nativeAccelerometer(event.values[0], event.values[1], event.values[2]); // TODO: not tested! - } + public synchronized void start() { + if ((Globals.UseAccelerometerAsArrowKeys || Globals.AppUsesAccelerometer) && + _manager != null && _manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null) { + Log.i("SDL", "libSDL: starting accelerometer"); + _manager.registerListener(this, _manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); + } + if ((Globals.AppUsesGyroscope || Globals.MoveMouseWithGyroscope) && + _manager != null && _manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null) { + Log.i("SDL", "libSDL: starting gyroscope"); + _manager.registerListener(gyro, _manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_GAME); + } + if ((Globals.AppUsesOrientationSensor) && _manager != null && + _manager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR) != null) { + Log.i("SDL", "libSDL: starting orientation sensor"); + _manager.registerListener(orientation, _manager.getDefaultSensor( + Sensor.TYPE_GAME_ROTATION_VECTOR), + SensorManager.SENSOR_DELAY_GAME); + } + } - public void onAccuracyChanged(Sensor s, int a) - { - } + public void onSensorChanged(SensorEvent event) { + if (Globals.HorizontalOrientation) { + if (gyro.invertedOrientation) + nativeAccelerometer(-event.values[1], event.values[0], event.values[2]); + else + nativeAccelerometer(event.values[1], -event.values[0], event.values[2]); + } else + nativeAccelerometer(event.values[0], event.values[1], event.values[2]); // TODO: not tested! + } - static class GyroscopeListener implements SensorEventListener - { - public boolean invertedOrientation = false; + public void onAccuracyChanged(Sensor s, int a) { + } - // Noise filter with sane initial values, so user will be able - // to move gyroscope during the first 10 seconds, while the noise is measured. - // After that the values are replaced by noiseMin/noiseMax. - final float filterMin[] = new float[] { -0.05f, -0.05f, -0.05f }; - final float filterMax[] = new float[] { 0.05f, 0.05f, 0.05f }; + static class GyroscopeListener implements SensorEventListener { + public boolean invertedOrientation = false; - // The noise levels we're measuring. - // Large initial values, they will decrease, but never increase. - float noiseMin[] = new float[] { -1.0f, -1.0f, -1.0f }; - float noiseMax[] = new float[] { 1.0f, 1.0f, 1.0f }; + // Noise filter with sane initial values, so user will be able + // to move gyroscope during the first 10 seconds, while the noise is measured. + // After that the values are replaced by noiseMin/noiseMax. + final float filterMin[] = new float[]{-0.05f, -0.05f, -0.05f}; + final float filterMax[] = new float[]{0.05f, 0.05f, 0.05f}; - // The gyro data buffer, from which we care calculating min/max noise values. - // The bigger it is, the more precise the calclations, and the longer it takes to converge. - float noiseData[][] = new float[200][noiseMin.length]; - int noiseDataIdx = 0; + // The noise levels we're measuring. + // Large initial values, they will decrease, but never increase. + float noiseMin[] = new float[]{-1.0f, -1.0f, -1.0f}; + float noiseMax[] = new float[]{1.0f, 1.0f, 1.0f}; - // When we detect movement, we remove last few values of the measured data. - // The movement is detected by comparing values to noiseMin/noiseMax of the previous iteration. - int movementBackoff = 0; + // The gyro data buffer, from which we care calculating min/max noise values. + // The bigger it is, the more precise the calclations, and the longer it takes to converge. + float noiseData[][] = new float[200][noiseMin.length]; + int noiseDataIdx = 0; - // Difference between min/max in the previous measurement iteration, - // used to determine when we should stop measuring, when the change becomes negligilbe. - float measuredNoiseRange[] = null; + // When we detect movement, we remove last few values of the measured data. + // The movement is detected by comparing values to noiseMin/noiseMax of the previous iteration. + int movementBackoff = 0; - // How long the algorithm is running, to stop it if it does not converge. - int measurementIteration = 0; + // Difference between min/max in the previous measurement iteration, + // used to determine when we should stop measuring, when the change becomes negligilbe. + float measuredNoiseRange[] = null; - public GyroscopeListener() - { - } + // How long the algorithm is running, to stop it if it does not converge. + int measurementIteration = 0; - void collectNoiseData(final float[] data) - { - for( int i = 0; i < noiseMin.length; i++ ) - { - if( data[i] < noiseMin[i] || data[i] > noiseMax[i] ) - { - // Movement detected, this can converge our min/max too early, so we're discarding last few values - if( movementBackoff < 0 ) - { - int discard = 10; - if( -movementBackoff < discard ) - discard = -movementBackoff; - noiseDataIdx -= discard; - if( noiseDataIdx < 0 ) - noiseDataIdx = 0; - } - movementBackoff = 10; - return; - } - noiseData[noiseDataIdx][i] = data[i]; - } - movementBackoff--; - if( movementBackoff >= 0 ) - return; // Also discard several values after the movement stopped - noiseDataIdx++; + public GyroscopeListener() { + } - if( noiseDataIdx < noiseData.length ) - return; + void collectNoiseData(final float[] data) { + for (int i = 0; i < noiseMin.length; i++) { + if (data[i] < noiseMin[i] || data[i] > noiseMax[i]) { + // Movement detected, this can converge our min/max too early, so we're discarding last few values + if (movementBackoff < 0) { + int discard = 10; + if (-movementBackoff < discard) + discard = -movementBackoff; + noiseDataIdx -= discard; + if (noiseDataIdx < 0) + noiseDataIdx = 0; + } + movementBackoff = 10; + return; + } + noiseData[noiseDataIdx][i] = data[i]; + } + movementBackoff--; + if (movementBackoff >= 0) + return; // Also discard several values after the movement stopped + noiseDataIdx++; - measurementIteration++; - Log.d( "SDL", "GYRO_NOISE: Measuring in progress... " + measurementIteration ); - if( measurementIteration > 5 ) - { - // We've collected enough data to use our noise min/max values as a new filter - System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length); - System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length); - } - if( measurementIteration > 15 ) - { - Log.d( "SDL", "GYRO_NOISE: Measuring done! Maximum number of iterations reached: " + measurementIteration ); - noiseData = null; - measuredNoiseRange = null; - return; - } + if (noiseDataIdx < noiseData.length) + return; - noiseDataIdx = 0; - boolean changed = false; - for( int i = 0; i < noiseMin.length; i++ ) - { - float min = 1.0f; - float max = -1.0f; - for( int ii = 0; ii < noiseData.length; ii++ ) - { - if( min > noiseData[ii][i] ) - min = noiseData[ii][i]; - if( max < noiseData[ii][i] ) - max = noiseData[ii][i]; - } - // Increase the range a bit, for safe conservative filtering - float middle = (min + max) / 2.0f; - min += (min - middle) * 0.2f; - max += (max - middle) * 0.2f; - // Check if range between min/max is less then the current range, as a safety measure, - // and min/max range is not jumping outside of previously measured range - if( max - min < noiseMax[i] - noiseMin[i] && min >= noiseMin[i] && max <= noiseMax[i] ) - { - // Move old min/max closer to the measured min/max, but do not replace the values altogether - noiseMin[i] = (noiseMin[i] + min * 4.0f) / 5.0f; - noiseMax[i] = (noiseMax[i] + max * 4.0f) / 5.0f; - changed = true; - } - } + measurementIteration++; + Log.d("SDL", "GYRO_NOISE: Measuring in progress... " + measurementIteration); + if (measurementIteration > 5) { + // We've collected enough data to use our noise min/max values as a new filter + System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length); + System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length); + } + if (measurementIteration > 15) { + Log.d("SDL", "GYRO_NOISE: Measuring done! Maximum number of iterations reached: " + measurementIteration); + noiseData = null; + measuredNoiseRange = null; + return; + } - Log.d( "SDL", "GYRO_NOISE: MIN MAX: " + Arrays.toString(noiseMin) + " " + Arrays.toString(noiseMax) ); + noiseDataIdx = 0; + boolean changed = false; + for (int i = 0; i < noiseMin.length; i++) { + float min = 1.0f; + float max = -1.0f; + for (int ii = 0; ii < noiseData.length; ii++) { + if (min > noiseData[ii][i]) + min = noiseData[ii][i]; + if (max < noiseData[ii][i]) + max = noiseData[ii][i]; + } + // Increase the range a bit, for safe conservative filtering + float middle = (min + max) / 2.0f; + min += (min - middle) * 0.2f; + max += (max - middle) * 0.2f; + // Check if range between min/max is less then the current range, as a safety measure, + // and min/max range is not jumping outside of previously measured range + if (max - min < noiseMax[i] - noiseMin[i] && min >= noiseMin[i] && max <= noiseMax[i]) { + // Move old min/max closer to the measured min/max, but do not replace the values altogether + noiseMin[i] = (noiseMin[i] + min * 4.0f) / 5.0f; + noiseMax[i] = (noiseMax[i] + max * 4.0f) / 5.0f; + changed = true; + } + } - if( !changed ) - return; + Log.d("SDL", "GYRO_NOISE: MIN MAX: " + Arrays.toString(noiseMin) + " " + Arrays.toString(noiseMax)); - // Determine when to stop measuring - check that the previous min/max range is close enough to the current one + if (!changed) + return; - float range[] = new float[noiseMin.length]; - for( int i = 0; i < noiseMin.length; i++ ) - range[i] = noiseMax[i] - noiseMin[i]; + // Determine when to stop measuring - check that the previous min/max range is close enough to the current one - Log.d( "SDL", "GYRO_NOISE: RANGE: " + Arrays.toString(range) + " " + Arrays.toString(measuredNoiseRange) ); + float range[] = new float[noiseMin.length]; + for (int i = 0; i < noiseMin.length; i++) + range[i] = noiseMax[i] - noiseMin[i]; - if( measuredNoiseRange == null ) - { - measuredNoiseRange = range; - return; // First iteration, skip further checks - } + Log.d("SDL", "GYRO_NOISE: RANGE: " + Arrays.toString(range) + " " + Arrays.toString(measuredNoiseRange)); - for( int i = 0; i < range.length; i++ ) - { - if( measuredNoiseRange[i] / range[i] > 1.2f ) - { - measuredNoiseRange = range; - return; - } - } + if (measuredNoiseRange == null) { + measuredNoiseRange = range; + return; // First iteration, skip further checks + } - // We converged to the final min/max filter values, stop measuring - System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length); - System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length); - noiseData = null; - measuredNoiseRange = null; - Log.d( "SDL", "GYRO_NOISE: Measuring done! Range converged on iteration " + measurementIteration ); - } + for (int i = 0; i < range.length; i++) { + if (measuredNoiseRange[i] / range[i] > 1.2f) { + measuredNoiseRange = range; + return; + } + } - public void onSensorChanged(final SensorEvent event) - { - boolean filtered = true; - final float[] data = event.values; + // We converged to the final min/max filter values, stop measuring + System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length); + System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length); + noiseData = null; + measuredNoiseRange = null; + Log.d("SDL", "GYRO_NOISE: Measuring done! Range converged on iteration " + measurementIteration); + } - if( noiseData != null ) - collectNoiseData(data); + public void onSensorChanged(final SensorEvent event) { + boolean filtered = true; + final float[] data = event.values; - for( int i = 0; i < 3; i++ ) - { - if( data[i] < filterMin[i] ) - { - filtered = false; - data[i] -= filterMin[i]; - } - else if( data[i] > filterMax[i] ) - { - filtered = false; - data[i] -= filterMax[i]; - } - } + if (noiseData != null) + collectNoiseData(data); - if( filtered ) - return; + for (int i = 0; i < 3; i++) { + if (data[i] < filterMin[i]) { + filtered = false; + data[i] -= filterMin[i]; + } else if (data[i] > filterMax[i]) { + filtered = false; + data[i] -= filterMax[i]; + } + } - if( Globals.HorizontalOrientation ) - { - if( invertedOrientation ) - nativeGyroscope(-data[0], -data[1], data[2]); - else - nativeGyroscope(data[0], data[1], data[2]); - } - else - { - if( invertedOrientation ) - nativeGyroscope(-data[1], data[0], data[2]); - else - nativeGyroscope(data[1], -data[0], data[2]); - } - } + if (filtered) + return; - public void onAccuracyChanged(Sensor s, int a) - { - } - public boolean available(AppCompatActivity context) - { - SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - return ( manager != null && manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null ); - } - public void registerListener(AppCompatActivity context, SensorEventListener l) - { - SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - if ( manager == null && manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) == null ) - return; - manager.registerListener(gyro, manager.getDefaultSensor( - Globals.AppUsesOrientationSensor ? Sensor.TYPE_GAME_ROTATION_VECTOR : Sensor.TYPE_GYROSCOPE), - SensorManager.SENSOR_DELAY_GAME); - } - public void unregisterListener(AppCompatActivity context, SensorEventListener l) - { - SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - if ( manager == null ) - return; - manager.unregisterListener(l); - } - } + if (Globals.HorizontalOrientation) { + if (invertedOrientation) + nativeGyroscope(-data[0], -data[1], data[2]); + else + nativeGyroscope(data[0], data[1], data[2]); + } else { + if (invertedOrientation) + nativeGyroscope(-data[1], data[0], data[2]); + else + nativeGyroscope(data[1], -data[0], data[2]); + } + } - static class OrientationListener implements SensorEventListener - { - public OrientationListener() - { - } - public void onSensorChanged(SensorEvent event) - { - nativeOrientation(event.values[0], event.values[1], event.values[2]); - } - public void onAccuracyChanged(Sensor s, int a) - { - } - } + public void onAccuracyChanged(Sensor s, int a) { + } - private static native void nativeAccelerometer(float accX, float accY, float accZ); - private static native void nativeGyroscope(float X, float Y, float Z); - private static native void nativeOrientation(float X, float Y, float Z); + public boolean available(AppCompatActivity context) { + SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + return (manager != null && manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null); + } + + public void registerListener(AppCompatActivity context, SensorEventListener l) { + SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + if (manager == null && manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) == null) + return; + manager.registerListener(gyro, manager.getDefaultSensor( + Globals.AppUsesOrientationSensor ? Sensor.TYPE_GAME_ROTATION_VECTOR : Sensor.TYPE_GYROSCOPE), + SensorManager.SENSOR_DELAY_GAME); + } + + public void unregisterListener(AppCompatActivity context, SensorEventListener l) { + SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + if (manager == null) + return; + manager.unregisterListener(l); + } + } + + static class OrientationListener implements SensorEventListener { + public OrientationListener() { + } + + public void onSensorChanged(SensorEvent event) { + nativeOrientation(event.values[0], event.values[1], event.values[2]); + } + + public void onAccuracyChanged(Sensor s, int a) { + } + } + + private static native void nativeAccelerometer(float accX, float accY, float accZ); + + private static native void nativeGyroscope(float X, float Y, float Z); + + private static native void nativeOrientation(float X, float Y, float Z); } diff --git a/Xorg/src/main/java/io/neoterm/Audio.java b/Xorg/src/main/java/io/neoterm/Audio.java index e664578..98c6025 100644 --- a/Xorg/src/main/java/io/neoterm/Audio.java +++ b/Xorg/src/main/java/io/neoterm/Audio.java @@ -23,300 +23,256 @@ freely, subject to the following restrictions: package io.neoterm; -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.view.MotionEvent; -import android.view.KeyEvent; -import android.view.Window; -import android.view.WindowManager; -import android.media.AudioTrack; -import android.media.AudioManager; import android.media.AudioFormat; +import android.media.AudioManager; import android.media.AudioRecord; +import android.media.AudioTrack; import android.media.MediaRecorder.AudioSource; -import java.io.*; import android.util.Log; -import java.util.concurrent.Semaphore; -import android.Manifest; -import android.content.pm.PackageManager; - import io.neoterm.xorg.NeoXorgViewClient; +import java.util.concurrent.Semaphore; + @SuppressWarnings("JniMissingFunction") -class AudioThread -{ - private NeoXorgViewClient mClient; - private AudioTrack mAudio; - private byte[] mAudioBuffer; - private int mVirtualBufSize; +class AudioThread { + private NeoXorgViewClient mClient; + private AudioTrack mAudio; + private byte[] mAudioBuffer; + private int mVirtualBufSize; - public AudioThread(NeoXorgViewClient client) - { - this.mClient = client; - mAudio = null; - mAudioBuffer = null; - nativeAudioInitJavaCallbacks(); - } - - public int fillBuffer() - { - if( mClient.isPaused() ) - { - try{ - Thread.sleep(500); - } catch (InterruptedException e) {} - } - else - { - //if( Globals.AudioBufferConfig == 0 ) // Gives too much spam to logcat, makes things worse - // mAudio.flush(); + public AudioThread(NeoXorgViewClient client) { + this.mClient = client; + mAudio = null; + mAudioBuffer = null; + nativeAudioInitJavaCallbacks(); + } - mAudio.write( mAudioBuffer, 0, mVirtualBufSize ); - } - - return 1; - } - - public int initAudio(int rate, int channels, int encoding, int bufSize) - { - if( mAudio == null ) - { - channels = ( channels == 1 ) ? AudioFormat.CHANNEL_CONFIGURATION_MONO : - AudioFormat.CHANNEL_CONFIGURATION_STEREO; - encoding = ( encoding == 1 ) ? AudioFormat.ENCODING_PCM_16BIT : - AudioFormat.ENCODING_PCM_8BIT; + public int fillBuffer() { + if (mClient.isPaused()) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + } + } else { + //if( Globals.AudioBufferConfig == 0 ) // Gives too much spam to logcat, makes things worse + // mAudio.flush(); - mVirtualBufSize = bufSize; + mAudio.write(mAudioBuffer, 0, mVirtualBufSize); + } - if( AudioTrack.getMinBufferSize( rate, channels, encoding ) > bufSize ) - bufSize = AudioTrack.getMinBufferSize( rate, channels, encoding ); + return 1; + } - if(Globals.AudioBufferConfig != 0) { // application's choice - use minimal buffer - bufSize = (int)((float)bufSize * (((float)(Globals.AudioBufferConfig - 1) * 2.5f) + 1.0f)); - mVirtualBufSize = bufSize; - } - mAudioBuffer = new byte[bufSize]; + public int initAudio(int rate, int channels, int encoding, int bufSize) { + if (mAudio == null) { + channels = (channels == 1) ? AudioFormat.CHANNEL_CONFIGURATION_MONO : + AudioFormat.CHANNEL_CONFIGURATION_STEREO; + encoding = (encoding == 1) ? AudioFormat.ENCODING_PCM_16BIT : + AudioFormat.ENCODING_PCM_8BIT; - mAudio = new AudioTrack(AudioManager.STREAM_MUSIC, - rate, - channels, - encoding, - bufSize, - AudioTrack.MODE_STREAM ); - mAudio.play(); - } - return mVirtualBufSize; - } - - public byte[] getBuffer() - { - return mAudioBuffer; - } - - public int deinitAudio() - { - if( mAudio != null ) - { - mAudio.stop(); - mAudio.release(); - mAudio = null; - } - mAudioBuffer = null; - return 1; - } - - public int initAudioThread() - { - // Make audio thread priority higher so audio thread won't get underrun - Thread.currentThread().setPriority(Thread.MAX_PRIORITY); - return 1; - } - - public int pauseAudioPlayback() - { - if( mAudio != null ) - { - mAudio.pause(); - } - if( mRecordThread != null ) - { - mRecordThread.pauseRecording(); - } - return 1; - } + mVirtualBufSize = bufSize; - public int resumeAudioPlayback() - { - if( mAudio != null ) - { - mAudio.play(); - } - if( mRecordThread != null ) - { - mRecordThread.resumeRecording(); - } - return 1; - } + if (AudioTrack.getMinBufferSize(rate, channels, encoding) > bufSize) + bufSize = AudioTrack.getMinBufferSize(rate, channels, encoding); - private native int nativeAudioInitJavaCallbacks(); + if (Globals.AudioBufferConfig != 0) { // application's choice - use minimal buffer + bufSize = (int) ((float) bufSize * (((float) (Globals.AudioBufferConfig - 1) * 2.5f) + 1.0f)); + mVirtualBufSize = bufSize; + } + mAudioBuffer = new byte[bufSize]; - // ----- Audio recording ----- + mAudio = new AudioTrack(AudioManager.STREAM_MUSIC, + rate, + channels, + encoding, + bufSize, + AudioTrack.MODE_STREAM); + mAudio.play(); + } + return mVirtualBufSize; + } - private RecordingThread mRecordThread = null; - private AudioRecord mRecorder = null; - private int mRecorderBufferSize = 0; + public byte[] getBuffer() { + return mAudioBuffer; + } - private byte[] startRecording(int rate, int channels, int encoding, int bufsize) - { - if( mRecordThread == null ) - { - mRecordThread = new RecordingThread(); - mRecordThread.start(); - } - if( !mRecordThread.isStopped() ) - { - Log.i("SDL", "SDL: error: application already opened audio recording device"); - return null; - } + public int deinitAudio() { + if (mAudio != null) { + mAudio.stop(); + mAudio.release(); + mAudio = null; + } + mAudioBuffer = null; + return 1; + } - mRecordThread.init(bufsize); + public int initAudioThread() { + // Make audio thread priority higher so audio thread won't get underrun + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); + return 1; + } - int channelConfig = ( channels == 1 ) ? AudioFormat.CHANNEL_IN_MONO : - AudioFormat.CHANNEL_IN_STEREO; - int encodingConfig = ( encoding == 1 ) ? AudioFormat.ENCODING_PCM_16BIT : - AudioFormat.ENCODING_PCM_8BIT; + public int pauseAudioPlayback() { + if (mAudio != null) { + mAudio.pause(); + } + if (mRecordThread != null) { + mRecordThread.pauseRecording(); + } + return 1; + } - int minBufDevice = AudioRecord.getMinBufferSize(rate, channelConfig, encodingConfig); - int minBufferSize = Math.max(bufsize * 8, minBufDevice + (bufsize - (minBufDevice % bufsize))); - Log.i("SDL", "SDL: app opened recording device, rate " + rate + " channels " + channels + " sample size " + (encoding+1) + " bufsize " + bufsize + " internal bufsize " + minBufferSize); - if( mRecorder == null || mRecorder.getSampleRate() != rate || - mRecorder.getChannelCount() != channels || - mRecorder.getAudioFormat() != encodingConfig || - mRecorderBufferSize != minBufferSize ) - { - if( mRecorder != null ) - mRecorder.release(); - mRecorder = null; - try { - mRecorder = new AudioRecord(AudioSource.MIC, rate, channelConfig, encodingConfig, minBufferSize); - mRecorderBufferSize = minBufferSize; - } catch (IllegalArgumentException e) { - Log.i("SDL", "SDL: error: failed to open MIC recording device!"); - try { - mRecorder = new AudioRecord(AudioSource.VOICE_RECOGNITION, rate, channelConfig, encodingConfig, minBufferSize); - mRecorderBufferSize = minBufferSize; - } catch (IllegalArgumentException eee) { - Log.i("SDL", "SDL: error: failed to open VOICE_RECOGNITION recording device!"); - try { - mRecorder = new AudioRecord(AudioSource.DEFAULT, rate, channelConfig, encodingConfig, minBufferSize); - mRecorderBufferSize = minBufferSize; - } catch (IllegalArgumentException eeee) { - Log.i("SDL", "SDL: error: failed to open DEFAULT recording device!"); - return null; - } - } - } - } - else - { - Log.i("SDL", "SDL: reusing old recording device"); - } - mRecordThread.startRecording(); - return mRecordThread.mRecordBuffer; - } + public int resumeAudioPlayback() { + if (mAudio != null) { + mAudio.play(); + } + if (mRecordThread != null) { + mRecordThread.resumeRecording(); + } + return 1; + } - private void stopRecording() - { - if( mRecordThread == null || mRecordThread.isStopped() ) - { - Log.i("SDL", "SDL: error: application already closed audio recording device"); - return; - } - mRecordThread.stopRecording(); - Log.i("SDL", "SDL: app closed recording device"); - } + private native int nativeAudioInitJavaCallbacks(); - private class RecordingThread extends Thread - { - private boolean stopped = true; - byte[] mRecordBuffer; - private Semaphore waitStarted = new Semaphore(0); - private boolean sleep = false; + // ----- Audio recording ----- - RecordingThread() - { - super(); - } + private RecordingThread mRecordThread = null; + private AudioRecord mRecorder = null; + private int mRecorderBufferSize = 0; - void init(int bufsize) - { - if( mRecordBuffer == null || mRecordBuffer.length != bufsize ) - mRecordBuffer = new byte[bufsize]; - } + private byte[] startRecording(int rate, int channels, int encoding, int bufsize) { + if (mRecordThread == null) { + mRecordThread = new RecordingThread(); + mRecordThread.start(); + } + if (!mRecordThread.isStopped()) { + Log.i("SDL", "SDL: error: application already opened audio recording device"); + return null; + } - public void run() - { - while( true ) - { - waitStarted.acquireUninterruptibly(); - waitStarted.drainPermits(); - stopped = false; - sleep = false; + mRecordThread.init(bufsize); - while( !sleep ) - { - int got = mRecorder.read(mRecordBuffer, 0, mRecordBuffer.length); - if( got != mRecordBuffer.length ) - { - // Audio is stopped here, sleep a bit. - try{ - Thread.sleep(1000); - } catch (InterruptedException e) {} - } - else - { - //Log.i("SDL", "SDL: nativeAudioRecordCallback with len " + mRecordBuffer.length); - nativeAudioRecordCallback(); - //Log.i("SDL", "SDL: nativeAudioRecordCallback returned"); - } - } + int channelConfig = (channels == 1) ? AudioFormat.CHANNEL_IN_MONO : + AudioFormat.CHANNEL_IN_STEREO; + int encodingConfig = (encoding == 1) ? AudioFormat.ENCODING_PCM_16BIT : + AudioFormat.ENCODING_PCM_8BIT; - stopped = true; - mRecorder.stop(); - } - } + int minBufDevice = AudioRecord.getMinBufferSize(rate, channelConfig, encodingConfig); + int minBufferSize = Math.max(bufsize * 8, minBufDevice + (bufsize - (minBufDevice % bufsize))); + Log.i("SDL", "SDL: app opened recording device, rate " + rate + " channels " + channels + " sample size " + (encoding + 1) + " bufsize " + bufsize + " internal bufsize " + minBufferSize); + if (mRecorder == null || mRecorder.getSampleRate() != rate || + mRecorder.getChannelCount() != channels || + mRecorder.getAudioFormat() != encodingConfig || + mRecorderBufferSize != minBufferSize) { + if (mRecorder != null) + mRecorder.release(); + mRecorder = null; + try { + mRecorder = new AudioRecord(AudioSource.MIC, rate, channelConfig, encodingConfig, minBufferSize); + mRecorderBufferSize = minBufferSize; + } catch (IllegalArgumentException e) { + Log.i("SDL", "SDL: error: failed to open MIC recording device!"); + try { + mRecorder = new AudioRecord(AudioSource.VOICE_RECOGNITION, rate, channelConfig, encodingConfig, minBufferSize); + mRecorderBufferSize = minBufferSize; + } catch (IllegalArgumentException eee) { + Log.i("SDL", "SDL: error: failed to open VOICE_RECOGNITION recording device!"); + try { + mRecorder = new AudioRecord(AudioSource.DEFAULT, rate, channelConfig, encodingConfig, minBufferSize); + mRecorderBufferSize = minBufferSize; + } catch (IllegalArgumentException eeee) { + Log.i("SDL", "SDL: error: failed to open DEFAULT recording device!"); + return null; + } + } + } + } else { + Log.i("SDL", "SDL: reusing old recording device"); + } + mRecordThread.startRecording(); + return mRecordThread.mRecordBuffer; + } - public void startRecording() - { - mRecorder.startRecording(); - waitStarted.release(); - } - public void stopRecording() - { - sleep = true; - while( !stopped ) - { - try{ - Thread.sleep(100); - } catch (InterruptedException e) {} - } - } - public void pauseRecording() - { - if( !stopped ) - mRecorder.stop(); - } - public void resumeRecording() - { - if( !stopped ) - mRecorder.startRecording(); - } - public boolean isStopped() - { - return stopped; - } - } + private void stopRecording() { + if (mRecordThread == null || mRecordThread.isStopped()) { + Log.i("SDL", "SDL: error: application already closed audio recording device"); + return; + } + mRecordThread.stopRecording(); + Log.i("SDL", "SDL: app closed recording device"); + } - private native void nativeAudioRecordCallback(); + private class RecordingThread extends Thread { + private boolean stopped = true; + byte[] mRecordBuffer; + private Semaphore waitStarted = new Semaphore(0); + private boolean sleep = false; + + RecordingThread() { + super(); + } + + void init(int bufsize) { + if (mRecordBuffer == null || mRecordBuffer.length != bufsize) + mRecordBuffer = new byte[bufsize]; + } + + public void run() { + while (true) { + waitStarted.acquireUninterruptibly(); + waitStarted.drainPermits(); + stopped = false; + sleep = false; + + while (!sleep) { + int got = mRecorder.read(mRecordBuffer, 0, mRecordBuffer.length); + if (got != mRecordBuffer.length) { + // Audio is stopped here, sleep a bit. + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + } else { + //Log.i("SDL", "SDL: nativeAudioRecordCallback with len " + mRecordBuffer.length); + nativeAudioRecordCallback(); + //Log.i("SDL", "SDL: nativeAudioRecordCallback returned"); + } + } + + stopped = true; + mRecorder.stop(); + } + } + + public void startRecording() { + mRecorder.startRecording(); + waitStarted.release(); + } + + public void stopRecording() { + sleep = true; + while (!stopped) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + } + } + + public void pauseRecording() { + if (!stopped) + mRecorder.stop(); + } + + public void resumeRecording() { + if (!stopped) + mRecorder.startRecording(); + } + + public boolean isStopped() { + return stopped; + } + } + + private native void nativeAudioRecordCallback(); } diff --git a/Xorg/src/main/java/io/neoterm/Clipboard.java b/Xorg/src/main/java/io/neoterm/Clipboard.java index a90488b..fa8452d 100644 --- a/Xorg/src/main/java/io/neoterm/Clipboard.java +++ b/Xorg/src/main/java/io/neoterm/Clipboard.java @@ -22,119 +22,94 @@ freely, subject to the following restrictions: package io.neoterm; -import android.os.Bundle; -import android.os.Build; -import android.os.Environment; -import android.util.DisplayMetrics; -import android.util.Log; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.AssetManager; -import android.app.Activity; -import android.view.MotionEvent; -import android.view.KeyEvent; -import android.view.InputDevice; -import android.view.Window; -import android.view.WindowManager; -import android.widget.TextView; -import android.widget.Toast; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; import android.content.ClipboardManager; import android.content.ClipboardManager.OnPrimaryClipChangedListener; -import android.app.PendingIntent; -import android.app.AlarmManager; -import android.content.Intent; -import android.view.View; -import android.view.Display; +import android.content.Context; +import android.os.Build; +import android.util.Log; -public abstract class Clipboard -{ - public static Clipboard get() - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) - return NewerClipboard.Holder.Instance; - return OlderClipboard.Holder.Instance; - } - public abstract void set(final Context context, final String text); - public abstract String get(final Context context); - public abstract void setListener(final Context context, final Runnable listener); +public abstract class Clipboard { + public static Clipboard get() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) + return NewerClipboard.Holder.Instance; + return OlderClipboard.Holder.Instance; + } - private static class NewerClipboard extends Clipboard - { - private static class Holder - { - private static final NewerClipboard Instance = new NewerClipboard(); - } - public void set(final Context context, final String text) - { - try { - ClipboardManager clipboard = (ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE); - if( clipboard != null ) - clipboard.setText(text); - } catch (Exception e) { - Log.i("SDL", "setClipboardText() exception: " + e.toString()); - } - } - public String get(final Context context) - { - String ret = ""; - try { - ClipboardManager clipboard = (ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE); - if( clipboard != null && clipboard.getText() != null ) - ret = clipboard.getText().toString(); - } catch (Exception e) { - Log.i("SDL", "getClipboardText() exception: " + e.toString()); - } - return ret; - } - public void setListener(final Context context, final Runnable listener) - { - ClipboardManager clipboard = (ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE); - clipboard.addPrimaryClipChangedListener(new OnPrimaryClipChangedListener() - { - public void onPrimaryClipChanged() - { - listener.run(); - } - }); - } - } + public abstract void set(final Context context, final String text); - private static class OlderClipboard extends Clipboard - { - private static class Holder - { - private static final OlderClipboard Instance = new OlderClipboard(); - } - public void set(final Context context, final String text) - { - try { - android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE); - if( clipboard != null ) - clipboard.setText(text); - } catch (Exception e) { - Log.i("SDL", "setClipboardText() exception: " + e.toString()); - } - } - public String get(final Context context) - { - String ret = ""; - try { - android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE); - if( clipboard != null && clipboard.getText() != null ) - ret = clipboard.getText().toString(); - } catch (Exception e) { - Log.i("SDL", "getClipboardText() exception: " + e.toString()); - } - return ret; - } - public void setListener(final Context context, final Runnable listener) - { - Log.i("SDL", "Cannot set clipboard listener on Android 2.3 or older"); - } - } + public abstract String get(final Context context); + + public abstract void setListener(final Context context, final Runnable listener); + + private static class NewerClipboard extends Clipboard { + private static class Holder { + private static final NewerClipboard Instance = new NewerClipboard(); + } + + public void set(final Context context, final String text) { + try { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE); + if (clipboard != null) + clipboard.setText(text); + } catch (Exception e) { + Log.i("SDL", "setClipboardText() exception: " + e.toString()); + } + } + + public String get(final Context context) { + String ret = ""; + try { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE); + if (clipboard != null && clipboard.getText() != null) + ret = clipboard.getText().toString(); + } catch (Exception e) { + Log.i("SDL", "getClipboardText() exception: " + e.toString()); + } + return ret; + } + + public void setListener(final Context context, final Runnable listener) { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE); + clipboard.addPrimaryClipChangedListener(new OnPrimaryClipChangedListener() { + public void onPrimaryClipChanged() { + listener.run(); + } + }); + } + } + + private static class OlderClipboard extends Clipboard { + private static class Holder { + private static final OlderClipboard Instance = new OlderClipboard(); + } + + public void set(final Context context, final String text) { + try { + android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE); + if (clipboard != null) + clipboard.setText(text); + } catch (Exception e) { + Log.i("SDL", "setClipboardText() exception: " + e.toString()); + } + } + + public String get(final Context context) { + String ret = ""; + try { + android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(context.CLIPBOARD_SERVICE); + if (clipboard != null && clipboard.getText() != null) + ret = clipboard.getText().toString(); + } catch (Exception e) { + Log.i("SDL", "getClipboardText() exception: " + e.toString()); + } + return ret; + } + + public void setListener(final Context context, final Runnable listener) { + Log.i("SDL", "Cannot set clipboard listener on Android 2.3 or older"); + } + } } diff --git a/Xorg/src/main/java/io/neoterm/GLSurfaceView_SDL.java b/Xorg/src/main/java/io/neoterm/GLSurfaceView_SDL.java index 96e8be1..685f154 100644 --- a/Xorg/src/main/java/io/neoterm/GLSurfaceView_SDL.java +++ b/Xorg/src/main/java/io/neoterm/GLSurfaceView_SDL.java @@ -20,27 +20,19 @@ package io.neoterm; -import java.io.Writer; -import java.util.ArrayList; -import java.util.concurrent.Semaphore; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGL11; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; -import javax.microedition.khronos.opengles.GL; -import javax.microedition.khronos.opengles.GL10; - -import android.opengl.EGL14; // Android 4.2 or newer - +import android.app.KeyguardManager; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; -import android.app.KeyguardManager; + +import javax.microedition.khronos.egl.*; +import javax.microedition.khronos.opengles.GL; +import javax.microedition.khronos.opengles.GL10; +import java.io.Writer; +import java.util.ArrayList; +import java.util.concurrent.Semaphore; /** * An implementation of SurfaceView that uses the dedicated surface for @@ -128,811 +120,828 @@ import android.app.KeyguardManager; *
  * class MyGLSurfaceView extends GLSurfaceView {
  *
- *	   private MyRenderer mMyRenderer;
+ * 	   private MyRenderer mMyRenderer;
  *
- *	   public void start() {
- *		   mMyRenderer = ...;
- *		   setRenderer(mMyRenderer);
- *	   }
+ * 	   public void start() {
+ * 		   mMyRenderer = ...;
+ * 		   setRenderer(mMyRenderer);
+ *     }
  *
- *	   public boolean onKeyDown(int keyCode, KeyEvent event) {
- *		   if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
- *			   queueEvent(new Runnable() {
- *				   // This method will be called on the rendering
- *				   // thread:
- *				   public void run() {
- *					   mMyRenderer.handleDpadCenter();
- *				   }});
- *			   return true;
- *		   }
- *		   return super.onKeyDown(keyCode, event);
- *	   }
+ * 	   public boolean onKeyDown(int keyCode, KeyEvent event) {
+ * 		   if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ * 			   queueEvent(new Runnable() {
+ * 				   // This method will be called on the rendering
+ * 				   // thread:
+ * 				   public void run() {
+ * 					   mMyRenderer.handleDpadCenter();
+ *           }});
+ * 			   return true;
+ *       }
+ * 		   return super.onKeyDown(keyCode, event);
+ *     }
  * }
  * 
- * */ @SuppressWarnings("ALL") public class GLSurfaceView_SDL extends SurfaceView implements SurfaceHolder.Callback { - /** - * The renderer only renders - * when the surface is created, or when {@link #requestRender} is called. - * - * @see #getRenderMode() - * @see #setRenderMode(int) - */ - public final static int RENDERMODE_WHEN_DIRTY = 0; - /** - * The renderer is called - * continuously to re-render the scene. - * - * @see #getRenderMode() - * @see #setRenderMode(int) - * @see #requestRender() - */ - public final static int RENDERMODE_CONTINUOUSLY = 1; + /** + * The renderer only renders + * when the surface is created, or when {@link #requestRender} is called. + * + * @see #getRenderMode() + * @see #setRenderMode(int) + */ + public final static int RENDERMODE_WHEN_DIRTY = 0; + /** + * The renderer is called + * continuously to re-render the scene. + * + * @see #getRenderMode() + * @see #setRenderMode(int) + * @see #requestRender() + */ + public final static int RENDERMODE_CONTINUOUSLY = 1; - /** - * Check glError() after every GL call and throw an exception if glError indicates - * that an error has occurred. This can be used to help track down which OpenGL ES call - * is causing an error. - * - * @see #getDebugFlags - * @see #setDebugFlags - */ - public final static int DEBUG_CHECK_GL_ERROR = 1; + /** + * Check glError() after every GL call and throw an exception if glError indicates + * that an error has occurred. This can be used to help track down which OpenGL ES call + * is causing an error. + * + * @see #getDebugFlags + * @see #setDebugFlags + */ + public final static int DEBUG_CHECK_GL_ERROR = 1; - /** - * Log GL calls to the system log at "verbose" level with tag "GLSurfaceView". - * - * @see #getDebugFlags - * @see #setDebugFlags - */ - public final static int DEBUG_LOG_GL_CALLS = 2; + /** + * Log GL calls to the system log at "verbose" level with tag "GLSurfaceView". + * + * @see #getDebugFlags + * @see #setDebugFlags + */ + public final static int DEBUG_LOG_GL_CALLS = 2; - /** - * Standard View constructor. In order to render something, you - * must call {@link #setRenderer} to register a renderer. - */ - public GLSurfaceView_SDL(Context context) { - super(context); - init(); - } + /** + * Standard View constructor. In order to render something, you + * must call {@link #setRenderer} to register a renderer. + */ + public GLSurfaceView_SDL(Context context) { + super(context); + init(); + } - /** - * Standard View constructor. In order to render something, you - * must call {@link #setRenderer} to register a renderer. - */ - public GLSurfaceView_SDL(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } + /** + * Standard View constructor. In order to render something, you + * must call {@link #setRenderer} to register a renderer. + */ + public GLSurfaceView_SDL(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } - private void init() { - // Install a SurfaceHolder.Callback so we get notified when the - // underlying surface is created and destroyed - SurfaceHolder holder = getHolder(); - holder.addCallback(this); - holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); - mKeyguardManager = ((KeyguardManager)getContext().getSystemService(Context.KEYGUARD_SERVICE)); - } + private void init() { + // Install a SurfaceHolder.Callback so we get notified when the + // underlying surface is created and destroyed + SurfaceHolder holder = getHolder(); + holder.addCallback(this); + holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); + mKeyguardManager = ((KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE)); + } - /** - * Set the glWrapper. If the glWrapper is not null, its - * {@link GLWrapper#wrap(GL)} method is called - * whenever a surface is created. A GLWrapper can be used to wrap - * the GL object that's passed to the renderer. Wrapping a GL - * object enables examining and modifying the behavior of the - * GL calls made by the renderer. - *

- * Wrapping is typically used for debugging purposes. - *

- * The default value is null. - * @param glWrapper the new GLWrapper - */ - public void setGLWrapper(GLWrapper glWrapper) { - mGLWrapper = glWrapper; - } + /** + * Set the glWrapper. If the glWrapper is not null, its + * {@link GLWrapper#wrap(GL)} method is called + * whenever a surface is created. A GLWrapper can be used to wrap + * the GL object that's passed to the renderer. Wrapping a GL + * object enables examining and modifying the behavior of the + * GL calls made by the renderer. + *

+ * Wrapping is typically used for debugging purposes. + *

+ * The default value is null. + * + * @param glWrapper the new GLWrapper + */ + public void setGLWrapper(GLWrapper glWrapper) { + mGLWrapper = glWrapper; + } - /** - * Set the debug flags to a new value. The value is - * constructed by OR-together zero or more - * of the DEBUG_CHECK_* constants. The debug flags take effect - * whenever a surface is created. The default value is zero. - * @param debugFlags the new debug flags - * @see #DEBUG_CHECK_GL_ERROR - * @see #DEBUG_LOG_GL_CALLS - */ - public void setDebugFlags(int debugFlags) { - mDebugFlags = debugFlags; - } + /** + * Set the debug flags to a new value. The value is + * constructed by OR-together zero or more + * of the DEBUG_CHECK_* constants. The debug flags take effect + * whenever a surface is created. The default value is zero. + * + * @param debugFlags the new debug flags + * @see #DEBUG_CHECK_GL_ERROR + * @see #DEBUG_LOG_GL_CALLS + */ + public void setDebugFlags(int debugFlags) { + mDebugFlags = debugFlags; + } - /** - * Get the current value of the debug flags. - * @return the current value of the debug flags. - */ - public int getDebugFlags() { - return mDebugFlags; - } + /** + * Get the current value of the debug flags. + * + * @return the current value of the debug flags. + */ + public int getDebugFlags() { + return mDebugFlags; + } - /** - * Set the renderer associated with this view. Also starts the thread that - * will call the renderer, which in turn causes the rendering to start. - *

This method should be called once and only once in the life-cycle of - * a GLSurfaceView. - *

The following GLSurfaceView methods can only be called before - * setRenderer is called: - *

    - *
  • {@link #setEGLConfigChooser(boolean)} - *
  • {@link #setEGLConfigChooser(EGLConfigChooser)} - *
  • {@link #setEGLConfigChooser(int, int, int, int, int, int)} - *
- *

- * The following GLSurfaceView methods can only be called after - * setRenderer is called: - *

    - *
  • {@link #getRenderMode()} - *
  • {@link #onPause()} - *
  • {@link #onResume()} - *
  • {@link #queueEvent(Runnable)} - *
  • {@link #requestRender()} - *
  • {@link #setRenderMode(int)} - *
- * - * @param renderer the renderer to use to perform OpenGL drawing. - */ - public void setRenderer(Renderer renderer) { - if (mGLThread != null) { - throw new IllegalStateException( - "setRenderer has already been called for this instance."); - } - if (mEGLConfigChooser == null) { - mEGLConfigChooser = getEglConfigChooser(16, false, false, false, false); - } - mGLThread = new GLThread(renderer); - mGLThread.start(); - } + /** + * Set the renderer associated with this view. Also starts the thread that + * will call the renderer, which in turn causes the rendering to start. + *

This method should be called once and only once in the life-cycle of + * a GLSurfaceView. + *

The following GLSurfaceView methods can only be called before + * setRenderer is called: + *

    + *
  • {@link #setEGLConfigChooser(boolean)} + *
  • {@link #setEGLConfigChooser(EGLConfigChooser)} + *
  • {@link #setEGLConfigChooser(int, int, int, int, int, int)} + *
+ *

+ * The following GLSurfaceView methods can only be called after + * setRenderer is called: + *

    + *
  • {@link #getRenderMode()} + *
  • {@link #onPause()} + *
  • {@link #onResume()} + *
  • {@link #queueEvent(Runnable)} + *
  • {@link #requestRender()} + *
  • {@link #setRenderMode(int)} + *
+ * + * @param renderer the renderer to use to perform OpenGL drawing. + */ + public void setRenderer(Renderer renderer) { + if (mGLThread != null) { + throw new IllegalStateException( + "setRenderer has already been called for this instance."); + } + if (mEGLConfigChooser == null) { + mEGLConfigChooser = getEglConfigChooser(16, false, false, false, false); + } + mGLThread = new GLThread(renderer); + mGLThread.start(); + } - /** - * Install a custom EGLConfigChooser. - *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} - * is called. - *

- * If no setEGLConfigChooser method is called, then by default the - * view will choose a config as close to 16-bit RGB as possible, with - * a depth buffer as close to 16 bits as possible. - * @param configChooser - */ - public void setEGLConfigChooser(EGLConfigChooser configChooser) { - if (mGLThread != null) { - throw new IllegalStateException( - "setRenderer has already been called for this instance."); - } - mEGLConfigChooser = configChooser; - } + /** + * Install a custom EGLConfigChooser. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If no setEGLConfigChooser method is called, then by default the + * view will choose a config as close to 16-bit RGB as possible, with + * a depth buffer as close to 16 bits as possible. + * + * @param configChooser + */ + public void setEGLConfigChooser(EGLConfigChooser configChooser) { + if (mGLThread != null) { + throw new IllegalStateException( + "setRenderer has already been called for this instance."); + } + mEGLConfigChooser = configChooser; + } - /** - * Install a config chooser which will choose a config - * as close to 16-bit RGB as possible, with or without an optional depth - * buffer as close to 16-bits as possible. - *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} - * is called. - *

- * If no setEGLConfigChooser method is called, then by default the - * view will choose a config as close to 16-bit RGB as possible, with - * a depth buffer as close to 16 bits as possible. - * - * @param needDepth - */ - public void setEGLConfigChooser(int bpp, boolean needDepth, boolean stencil, boolean gles2, boolean gles3) { - setEGLConfigChooser(getEglConfigChooser(bpp, needDepth, stencil, gles2, gles3)); - } + /** + * Install a config chooser which will choose a config + * as close to 16-bit RGB as possible, with or without an optional depth + * buffer as close to 16-bits as possible. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If no setEGLConfigChooser method is called, then by default the + * view will choose a config as close to 16-bit RGB as possible, with + * a depth buffer as close to 16 bits as possible. + * + * @param needDepth + */ + public void setEGLConfigChooser(int bpp, boolean needDepth, boolean stencil, boolean gles2, boolean gles3) { + setEGLConfigChooser(getEglConfigChooser(bpp, needDepth, stencil, gles2, gles3)); + } - /** - * Install a config chooser which will choose a config - * with at least the specified component sizes, and as close - * to the specified component sizes as possible. - *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} - * is called. - *

- * If no setEGLConfigChooser method is called, then by default the - * view will choose a config as close to 16-bit RGB as possible, with - * a depth buffer as close to 16 bits as possible. - * - */ - public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, - int alphaSize, int depthSize, int stencilSize, boolean gles2, boolean gles3) { - setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize, - blueSize, alphaSize, depthSize, stencilSize, gles2, gles3)); - } - /** - * Set the rendering mode. When renderMode is - * RENDERMODE_CONTINUOUSLY, the renderer is called - * repeatedly to re-render the scene. When renderMode - * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface - * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. - *

- * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance - * by allowing the GPU and CPU to idle when the view does not need to be updated. - *

- * This method can only be called after {@link #setRenderer(Renderer)} - * - * @param renderMode one of the RENDERMODE_X constants - * @see #RENDERMODE_CONTINUOUSLY - * @see #RENDERMODE_WHEN_DIRTY - */ - public void setRenderMode(int renderMode) { - mGLThread.setRenderMode(renderMode); - } + /** + * Install a config chooser which will choose a config + * with at least the specified component sizes, and as close + * to the specified component sizes as possible. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If no setEGLConfigChooser method is called, then by default the + * view will choose a config as close to 16-bit RGB as possible, with + * a depth buffer as close to 16 bits as possible. + */ + public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize, boolean gles2, boolean gles3) { + setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize, + blueSize, alphaSize, depthSize, stencilSize, gles2, gles3)); + } - /** - * Get the current rendering mode. May be called - * from any thread. Must not be called before a renderer has been set. - * @return the current rendering mode. - * @see #RENDERMODE_CONTINUOUSLY - * @see #RENDERMODE_WHEN_DIRTY - */ - public int getRenderMode() { - return mGLThread.getRenderMode(); - } + /** + * Set the rendering mode. When renderMode is + * RENDERMODE_CONTINUOUSLY, the renderer is called + * repeatedly to re-render the scene. When renderMode + * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface + * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. + *

+ * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance + * by allowing the GPU and CPU to idle when the view does not need to be updated. + *

+ * This method can only be called after {@link #setRenderer(Renderer)} + * + * @param renderMode one of the RENDERMODE_X constants + * @see #RENDERMODE_CONTINUOUSLY + * @see #RENDERMODE_WHEN_DIRTY + */ + public void setRenderMode(int renderMode) { + mGLThread.setRenderMode(renderMode); + } - /** - * Request that the renderer render a frame. - * This method is typically used when the render mode has been set to - * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand. - * May be called - * from any thread. Must not be called before a renderer has been set. - */ - public void requestRender() { - mGLThread.requestRender(); - } + /** + * Get the current rendering mode. May be called + * from any thread. Must not be called before a renderer has been set. + * + * @return the current rendering mode. + * @see #RENDERMODE_CONTINUOUSLY + * @see #RENDERMODE_WHEN_DIRTY + */ + public int getRenderMode() { + return mGLThread.getRenderMode(); + } - /** - * This method is part of the SurfaceHolder.Callback interface, and is - * not normally called or subclassed by clients of GLSurfaceView. - */ - public void surfaceCreated(SurfaceHolder holder) { - mGLThread.surfaceCreated(); - } + /** + * Request that the renderer render a frame. + * This method is typically used when the render mode has been set to + * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand. + * May be called + * from any thread. Must not be called before a renderer has been set. + */ + public void requestRender() { + mGLThread.requestRender(); + } - /** - * This method is part of the SurfaceHolder.Callback interface, and is - * not normally called or subclassed by clients of GLSurfaceView. - */ - public void surfaceDestroyed(SurfaceHolder holder) { - // Surface will be destroyed when we return - mGLThread.surfaceDestroyed(); - } + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of GLSurfaceView. + */ + public void surfaceCreated(SurfaceHolder holder) { + mGLThread.surfaceCreated(); + } - /** - * This method is part of the SurfaceHolder.Callback interface, and is - * not normally called or subclassed by clients of GLSurfaceView. - */ - public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { - mGLThread.onWindowResize(w, h); - } + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of GLSurfaceView. + */ + public void surfaceDestroyed(SurfaceHolder holder) { + // Surface will be destroyed when we return + mGLThread.surfaceDestroyed(); + } - /** - * Inform the view that the activity is paused. The owner of this view must - * call this method when the activity is paused. Calling this method will - * pause the rendering thread. - * Must not be called before a renderer has been set. - */ - public void onPause() { - mGLThread.onPause(); - } + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of GLSurfaceView. + */ + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + mGLThread.onWindowResize(w, h); + } - /** - * Inform the view that the activity is resumed. The owner of this view must - * call this method when the activity is resumed. Calling this method will - * recreate the OpenGL display and resume the rendering - * thread. - * Must not be called before a renderer has been set. - */ - public void onResume() { - mGLThread.onResume(); - } + /** + * Inform the view that the activity is paused. The owner of this view must + * call this method when the activity is paused. Calling this method will + * pause the rendering thread. + * Must not be called before a renderer has been set. + */ + public void onPause() { + mGLThread.onPause(); + } - /** - * Queue a runnable to be run on the GL rendering thread. This can be used - * to communicate with the Renderer on the rendering thread. - * Must not be called before a renderer has been set. - * @param r the runnable to be run on the GL rendering thread. - */ - public void queueEvent(Runnable r) { - mGLThread.queueEvent(r); - } + /** + * Inform the view that the activity is resumed. The owner of this view must + * call this method when the activity is resumed. Calling this method will + * recreate the OpenGL display and resume the rendering + * thread. + * Must not be called before a renderer has been set. + */ + public void onResume() { + mGLThread.onResume(); + } - /** - * This method is used as part of the View class and is not normally - * called or subclassed by clients of GLSurfaceView. - * Must not be called before a renderer has been set. - */ - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mGLThread.requestExitAndWait(); - } + /** + * Queue a runnable to be run on the GL rendering thread. This can be used + * to communicate with the Renderer on the rendering thread. + * Must not be called before a renderer has been set. + * + * @param r the runnable to be run on the GL rendering thread. + */ + public void queueEvent(Runnable r) { + mGLThread.queueEvent(r); + } - // ---------------------------------------------------------------------- + /** + * This method is used as part of the View class and is not normally + * called or subclassed by clients of GLSurfaceView. + * Must not be called before a renderer has been set. + */ + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mGLThread.requestExitAndWait(); + } - /** - * An interface used to wrap a GL interface. - *

Typically - * used for implementing debugging and tracing on top of the default - * GL interface. You would typically use this by creating your own class - * that implemented all the GL methods by delegating to another GL instance. - * Then you could add your own behavior before or after calling the - * delegate. All the GLWrapper would do was instantiate and return the - * wrapper GL instance: - *

-	 * class MyGLWrapper implements GLWrapper {
-	 *	   GL wrap(GL gl) {
-	 *		   return new MyGLImplementation(gl);
-	 *	   }
-	 *	   static class MyGLImplementation implements GL,GL10,GL11,... {
-	 *		   ...
-	 *	   }
-	 * }
-	 * 
- * @see #setGLWrapper(GLWrapper) - */ - public interface GLWrapper { - /** - * Wraps a gl interface in another gl interface. - * @param gl a GL interface that is to be wrapped. - * @return either the input argument or another GL object that wraps the input argument. - */ - GL wrap(GL gl); - } + // ---------------------------------------------------------------------- - /** - * A generic renderer interface. - *

- * The renderer is responsible for making OpenGL calls to render a frame. - *

- * GLSurfaceView clients typically create their own classes that implement - * this interface, and then call {@link GLSurfaceView#setRenderer} to - * register the renderer with the GLSurfaceView. - *

- *

Threading

- * The renderer will be called on a separate thread, so that rendering - * performance is decoupled from the UI thread. Clients typically need to - * communicate with the renderer from the UI thread, because that's where - * input events are received. Clients can communicate using any of the - * standard Java techniques for cross-thread communication, or they can - * use the {@link GLSurfaceView#queueEvent(Runnable)} convenience method. - *

- *

EGL Context Lost

- * There are situations where the EGL rendering context will be lost. This - * typically happens when device wakes up after going to sleep. When - * the EGL context is lost, all OpenGL resources (such as textures) that are - * associated with that context will be automatically deleted. In order to - * keep rendering correctly, a renderer must recreate any lost resources - * that it still needs. The {@link #onSurfaceCreated(GL10, EGLConfig)} method - * is a convenient place to do this. - * - * - * @see #setRenderer(Renderer) - */ - public static interface SwapBuffersCallback { - public boolean SwapBuffers(); - public void ResetVideoSurface(); - public void onWindowResize(int width, int height); - } + /** + * An interface used to wrap a GL interface. + *

Typically + * used for implementing debugging and tracing on top of the default + * GL interface. You would typically use this by creating your own class + * that implemented all the GL methods by delegating to another GL instance. + * Then you could add your own behavior before or after calling the + * delegate. All the GLWrapper would do was instantiate and return the + * wrapper GL instance: + *

+   * class MyGLWrapper implements GLWrapper {
+   * 	   GL wrap(GL gl) {
+   * 		   return new MyGLImplementation(gl);
+   *     }
+   * 	   static class MyGLImplementation implements GL,GL10,GL11,... {
+   * 		   ...
+   *     }
+   * }
+   * 
+ * + * @see #setGLWrapper(GLWrapper) + */ + public interface GLWrapper { + /** + * Wraps a gl interface in another gl interface. + * + * @param gl a GL interface that is to be wrapped. + * @return either the input argument or another GL object that wraps the input argument. + */ + GL wrap(GL gl); + } - public static abstract class Renderer { - /** - * Called when the surface is created or recreated. - *

- * Called when the rendering thread - * starts and whenever the EGL context is lost. The context will typically - * be lost when the Android device awakes after going to sleep. - *

- * Since this method is called at the beginning of rendering, as well as - * every time the EGL context is lost, this method is a convenient place to put - * code to create resources that need to be created when the rendering - * starts, and that need to be recreated when the EGL context is lost. - * Textures are an example of a resource that you might want to create - * here. - *

- * Note that when the EGL context is lost, all OpenGL resources associated - * with that context will be automatically deleted. You do not need to call - * the corresponding "glDelete" methods such as glDeleteTextures to - * manually delete these lost resources. - *

- * @param gl the GL interface. Use instanceof to - * test if the interface supports GL11 or higher interfaces. - * @param config the EGLConfig of the created surface. Can be used - * to create matching pbuffers. - */ - public abstract void onSurfaceCreated(GL10 gl, EGLConfig config); + /** + * A generic renderer interface. + *

+ * The renderer is responsible for making OpenGL calls to render a frame. + *

+ * GLSurfaceView clients typically create their own classes that implement + * this interface, and then call {@link GLSurfaceView#setRenderer} to + * register the renderer with the GLSurfaceView. + *

+ *

Threading

+ * The renderer will be called on a separate thread, so that rendering + * performance is decoupled from the UI thread. Clients typically need to + * communicate with the renderer from the UI thread, because that's where + * input events are received. Clients can communicate using any of the + * standard Java techniques for cross-thread communication, or they can + * use the {@link GLSurfaceView#queueEvent(Runnable)} convenience method. + *

+ *

EGL Context Lost

+ * There are situations where the EGL rendering context will be lost. This + * typically happens when device wakes up after going to sleep. When + * the EGL context is lost, all OpenGL resources (such as textures) that are + * associated with that context will be automatically deleted. In order to + * keep rendering correctly, a renderer must recreate any lost resources + * that it still needs. The {@link #onSurfaceCreated(GL10, EGLConfig)} method + * is a convenient place to do this. + * + * @see #setRenderer(Renderer) + */ + public static interface SwapBuffersCallback { + public boolean SwapBuffers(); - public abstract void onSurfaceDestroyed(); + public void ResetVideoSurface(); - /** - * Called when the surface changed size. - *

- * Called after the surface is created and whenever - * the OpenGL ES surface size changes. - *

- * Typically you will set your viewport here. If your camera - * is fixed then you could also set your projection matrix here: - *

-		 * void onSurfaceChanged(GL10 gl, int width, int height) {
-		 *	   gl.glViewport(0, 0, width, height);
-		 *	   // for a fixed camera, set the projection too
-		 *	   float ratio = (float) width / height;
-		 *	   gl.glMatrixMode(GL10.GL_PROJECTION);
-		 *	   gl.glLoadIdentity();
-		 *	   gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
-		 * }
-		 * 
- * @param gl the GL interface. Use instanceof to - * test if the interface supports GL11 or higher interfaces. - * @param width - * @param height - */ - public abstract void onSurfaceChanged(GL10 gl, int width, int height); + public void onWindowResize(int width, int height); + } - /** Called when screen size changes */ - public void onWindowResize(int width, int height) - { - if( mSwapBuffersCallback != null ) - mSwapBuffersCallback.onWindowResize(width, height); - } + public static abstract class Renderer { + /** + * Called when the surface is created or recreated. + *

+ * Called when the rendering thread + * starts and whenever the EGL context is lost. The context will typically + * be lost when the Android device awakes after going to sleep. + *

+ * Since this method is called at the beginning of rendering, as well as + * every time the EGL context is lost, this method is a convenient place to put + * code to create resources that need to be created when the rendering + * starts, and that need to be recreated when the EGL context is lost. + * Textures are an example of a resource that you might want to create + * here. + *

+ * Note that when the EGL context is lost, all OpenGL resources associated + * with that context will be automatically deleted. You do not need to call + * the corresponding "glDelete" methods such as glDeleteTextures to + * manually delete these lost resources. + *

+ * + * @param gl the GL interface. Use instanceof to + * test if the interface supports GL11 or higher interfaces. + * @param config the EGLConfig of the created surface. Can be used + * to create matching pbuffers. + */ + public abstract void onSurfaceCreated(GL10 gl, EGLConfig config); - /** - * Called to draw the current frame. - *

- * This method is responsible for drawing the current frame. - *

- * The implementation of this method typically looks like this: - *

-		 * void onDrawFrame(GL10 gl) {
-		 *	   gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
-		 *	   //... other gl calls to render the scene ...
-		 * }
-		 * 
- * @param gl the GL interface. Use instanceof to - * test if the interface supports GL11 or higher interfaces. - */ - public abstract void onDrawFrame(GL10 gl); - - public boolean SwapBuffers() { - if( mSwapBuffersCallback != null ) - return mSwapBuffersCallback.SwapBuffers(); - return false; - } + public abstract void onSurfaceDestroyed(); - public void ResetVideoSurface() { - if( mSwapBuffersCallback != null ) - mSwapBuffersCallback.ResetVideoSurface(); - } - - public void setSwapBuffersCallback( SwapBuffersCallback c ) { - mSwapBuffersCallback = c; - } + /** + * Called when the surface changed size. + *

+ * Called after the surface is created and whenever + * the OpenGL ES surface size changes. + *

+ * Typically you will set your viewport here. If your camera + * is fixed then you could also set your projection matrix here: + *

+     * void onSurfaceChanged(GL10 gl, int width, int height) {
+     * 	   gl.glViewport(0, 0, width, height);
+     * 	   // for a fixed camera, set the projection too
+     * 	   float ratio = (float) width / height;
+     * 	   gl.glMatrixMode(GL10.GL_PROJECTION);
+     * 	   gl.glLoadIdentity();
+     * 	   gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
+     * }
+     * 
+ * + * @param gl the GL interface. Use instanceof to + * test if the interface supports GL11 or higher interfaces. + * @param width + * @param height + */ + public abstract void onSurfaceChanged(GL10 gl, int width, int height); - private SwapBuffersCallback mSwapBuffersCallback = null; - } + /** + * Called when screen size changes + */ + public void onWindowResize(int width, int height) { + if (mSwapBuffersCallback != null) + mSwapBuffersCallback.onWindowResize(width, height); + } - /** - * An interface for choosing an EGLConfig configuration from a list of - * potential configurations. - *

- * This interface must be implemented by clients wishing to call - * {@link GLSurfaceView#setEGLConfigChooser(EGLConfigChooser)} - */ - public interface EGLConfigChooser { - /** - * Choose a configuration from the list. Implementors typically - * implement this method by calling - * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the - * EGL specification available from The Khronos Group to learn how to call eglChooseConfig. - * @param egl the EGL10 for the current display. - * @param display the current display. - * @return the chosen configuration. - */ - EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); - public boolean isGles2Required(); - public boolean isGles3Required(); - } + /** + * Called to draw the current frame. + *

+ * This method is responsible for drawing the current frame. + *

+ * The implementation of this method typically looks like this: + *

+     * void onDrawFrame(GL10 gl) {
+     * 	   gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+     * 	   //... other gl calls to render the scene ...
+     * }
+     * 
+ * + * @param gl the GL interface. Use instanceof to + * test if the interface supports GL11 or higher interfaces. + */ + public abstract void onDrawFrame(GL10 gl); - private static abstract class BaseConfigChooser - implements EGLConfigChooser { - public BaseConfigChooser(int[] configSpec) { - mConfigSpec = configSpec; - } - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { - int[] num_config = new int[1]; - egl.eglChooseConfig(display, mConfigSpec, null, 0, num_config); + public boolean SwapBuffers() { + if (mSwapBuffersCallback != null) + return mSwapBuffersCallback.SwapBuffers(); + return false; + } - int numConfigs = num_config[0]; + public void ResetVideoSurface() { + if (mSwapBuffersCallback != null) + mSwapBuffersCallback.ResetVideoSurface(); + } - if (numConfigs <= 0) { - throw new IllegalArgumentException( - "No configs match configSpec"); - } + public void setSwapBuffersCallback(SwapBuffersCallback c) { + mSwapBuffersCallback = c; + } - EGLConfig[] configs = new EGLConfig[numConfigs]; - egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, - num_config); - EGLConfig config = chooseConfig(egl, display, configs); - if (config == null) { - throw new IllegalArgumentException("No config chosen"); - } - return config; - } + private SwapBuffersCallback mSwapBuffersCallback = null; + } - abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, - EGLConfig[] configs); + /** + * An interface for choosing an EGLConfig configuration from a list of + * potential configurations. + *

+ * This interface must be implemented by clients wishing to call + * {@link GLSurfaceView#setEGLConfigChooser(EGLConfigChooser)} + */ + public interface EGLConfigChooser { + /** + * Choose a configuration from the list. Implementors typically + * implement this method by calling + * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the + * EGL specification available from The Khronos Group to learn how to call eglChooseConfig. + * + * @param egl the EGL10 for the current display. + * @param display the current display. + * @return the chosen configuration. + */ + EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); - protected int[] mConfigSpec; - } + public boolean isGles2Required(); - private static class ComponentSizeChooser extends BaseConfigChooser { - public ComponentSizeChooser(int redSize, int greenSize, int blueSize, - int alphaSize, int depthSize, int stencilSize, boolean isGles2, boolean isGles3) { - super(new int[] {EGL10.EGL_NONE}); // Get all possible configs - mValue = new int[1]; - mRedSize = redSize; - mGreenSize = greenSize; - mBlueSize = blueSize; - mAlphaSize = alphaSize; - mDepthSize = depthSize; - mStencilSize = stencilSize; - mIsGles2 = isGles2; - mIsGles3 = isGles3; - } + public boolean isGles3Required(); + } - @Override - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, - EGLConfig[] configs) { - EGLConfig closestConfig = null; - int closestDistance = 1000; - String cfglog = ""; - int idx = 0; - int selectidx = -1; + private static abstract class BaseConfigChooser + implements EGLConfigChooser { + public BaseConfigChooser(int[] configSpec) { + mConfigSpec = configSpec; + } - Log.v("SDL", "Desired GL config: " + "R" + mRedSize + "G" + mGreenSize + "B" + mBlueSize + "A" + mAlphaSize + " depth " + mDepthSize + " stencil " + mStencilSize + " type " + (mIsGles3 ? "GLES3" : mIsGles2 ? "GLES2" : "GLES")); - for(EGLConfig config : configs) { - if ( config == null ) - continue; - int r = findConfigAttrib(egl, display, config, - EGL10.EGL_RED_SIZE, 0); - int g = findConfigAttrib(egl, display, config, - EGL10.EGL_GREEN_SIZE, 0); - int b = findConfigAttrib(egl, display, config, - EGL10.EGL_BLUE_SIZE, 0); - int a = findConfigAttrib(egl, display, config, - EGL10.EGL_ALPHA_SIZE, 0); - int d = findConfigAttrib(egl, display, config, - EGL10.EGL_DEPTH_SIZE, 0); - int s = findConfigAttrib(egl, display, config, - EGL10.EGL_STENCIL_SIZE, 0); - int rendertype = findConfigAttrib(egl, display, config, - EGL10.EGL_RENDERABLE_TYPE, 0); - int desiredtype = mIsGles3 ? EGL_OPENGL_ES3_BIT : mIsGles2 ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES_BIT; - int nativeRender = findConfigAttrib(egl, display, config, - EGL10.EGL_NATIVE_RENDERABLE, 0); - int caveat = findConfigAttrib(egl, display, config, - EGL10.EGL_CONFIG_CAVEAT, EGL10.EGL_NONE); - int distance = Math.abs(r - mRedSize) + Math.abs(g - mGreenSize) + Math.abs(b - mBlueSize); - int dist1 = distance; - if( mAlphaSize - a > 0 ) - distance += mAlphaSize - a; - else if( mAlphaSize - a < 0 ) - distance += 1; // Small penalty if we don't need alpha channel but it is present - int dist2 = distance; - if( (d > 0) != (mDepthSize > 0) ) - distance += (mDepthSize > 0) ? 5 : 1; // Small penalty if we don't need zbuffer but it is present - int dist3 = distance; - if( (s > 0) != (mStencilSize > 0) ) - distance += (mStencilSize > 0) ? 5 : 1; // Small penalty if we don't need stencil buffer but it is present - int dist4 = distance; - if( (rendertype & desiredtype) == 0 ) - distance += 5; - int dist5 = distance; - if( caveat == EGL10.EGL_SLOW_CONFIG ) - distance += 4; - if( caveat == EGL10.EGL_NON_CONFORMANT_CONFIG ) // dunno what that means, probably R and B channels swapped - distance += 1; + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + int[] num_config = new int[1]; + egl.eglChooseConfig(display, mConfigSpec, null, 0, num_config); - String cfgcur = "R" + r + "G" + g + "B" + b + "A" + a + " depth " + d + " stencil " + s + - " type " + rendertype + " ("; - if((rendertype & EGL_OPENGL_ES_BIT) != 0) - cfgcur += "GLES"; - if((rendertype & EGL_OPENGL_ES2_BIT) != 0) - cfgcur += " GLES2"; - if((rendertype & EGL_OPENGL_ES3_BIT) != 0) - cfgcur += " GLES3"; - if((rendertype & EGL_OPENGL_BIT) != 0) - cfgcur += " OPENGL"; - if((rendertype & EGL_OPENVG_BIT) != 0) - cfgcur += " OPENVG"; - cfgcur += ")"; - cfgcur += " caveat " + (caveat == EGL10.EGL_NONE ? "none" : - (caveat == EGL10.EGL_SLOW_CONFIG ? "SLOW" : - caveat == EGL10.EGL_NON_CONFORMANT_CONFIG ? "non-conformant" : - String.valueOf(caveat))); - cfgcur += " nr " + nativeRender; - cfgcur += " pos " + distance + " (" + dist1 + "," + dist2 + "," + dist3 + "," + dist4 + "," + dist5 + ")"; - Log.v("SDL", "GL config " + idx + ": " + cfgcur); - if (distance < closestDistance) { - closestDistance = distance; - closestConfig = config; - cfglog = new String(cfgcur); - selectidx = idx; - } - idx += 1; - } - Log.v("SDL", "GLSurfaceView_SDL::EGLConfigChooser::chooseConfig(): selected " + selectidx + ": " + cfglog ); - return closestConfig; - } + int numConfigs = num_config[0]; - private int findConfigAttrib(EGL10 egl, EGLDisplay display, - EGLConfig config, int attribute, int defaultValue) { - mValue[0] = -1; - if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { - return mValue[0]; - } - Log.w("SDL", "GLSurfaceView_SDL::EGLConfigChooser::findConfigAttrib(): attribute doesn't exist: " + attribute); - return defaultValue; - } + if (numConfigs <= 0) { + throw new IllegalArgumentException( + "No configs match configSpec"); + } - public boolean isGles2Required() - { - return mIsGles2; - } + EGLConfig[] configs = new EGLConfig[numConfigs]; + egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, + num_config); + EGLConfig config = chooseConfig(egl, display, configs); + if (config == null) { + throw new IllegalArgumentException("No config chosen"); + } + return config; + } - public boolean isGles3Required() - { - return mIsGles3; - } + abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs); - private int[] mValue; - // Subclasses can adjust these values: - protected int mRedSize; - protected int mGreenSize; - protected int mBlueSize; - protected int mAlphaSize; - protected int mDepthSize; - protected int mStencilSize; - protected boolean mIsGles2 = false; - protected boolean mIsGles3 = false; + protected int[] mConfigSpec; + } - public static final int EGL_OPENGL_ES_BIT = 1; - public static final int EGL_OPENVG_BIT = 2; - public static final int EGL_OPENGL_ES2_BIT = 4; - public static final int EGL_OPENGL_BIT = 8; - public static final int EGL_OPENGL_ES3_BIT = 16; - } + private static class ComponentSizeChooser extends BaseConfigChooser { + public ComponentSizeChooser(int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize, boolean isGles2, boolean isGles3) { + super(new int[]{EGL10.EGL_NONE}); // Get all possible configs + mValue = new int[1]; + mRedSize = redSize; + mGreenSize = greenSize; + mBlueSize = blueSize; + mAlphaSize = alphaSize; + mDepthSize = depthSize; + mStencilSize = stencilSize; + mIsGles2 = isGles2; + mIsGles3 = isGles3; + } - /** - * This class will choose a supported surface as close to - * RGB565 as possible, with or without a depth buffer. - * - */ - private static class SimpleEGLConfigChooser16 extends ComponentSizeChooser { - public SimpleEGLConfigChooser16(boolean withDepthBuffer, boolean stencil, boolean gles2, boolean gles3) { - super(4, 4, 4, 0, withDepthBuffer ? 16 : 0, stencil ? 8 : 0, gles2, gles3); - // Adjust target values. This way we'll accept a 4444 or - // 555 buffer if there's no 565 buffer available. - mRedSize = 5; - mGreenSize = 6; - mBlueSize = 5; - } - } + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs) { + EGLConfig closestConfig = null; + int closestDistance = 1000; + String cfglog = ""; + int idx = 0; + int selectidx = -1; - private static class SimpleEGLConfigChooser24 extends ComponentSizeChooser { - public SimpleEGLConfigChooser24(boolean withDepthBuffer, boolean stencil, boolean gles2, boolean gles3) { - super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, stencil ? 8 : 0, gles2, gles3); - mRedSize = 8; - mGreenSize = 8; - mBlueSize = 8; - } - } + Log.v("SDL", "Desired GL config: " + "R" + mRedSize + "G" + mGreenSize + "B" + mBlueSize + "A" + mAlphaSize + " depth " + mDepthSize + " stencil " + mStencilSize + " type " + (mIsGles3 ? "GLES3" : mIsGles2 ? "GLES2" : "GLES")); + for (EGLConfig config : configs) { + if (config == null) + continue; + int r = findConfigAttrib(egl, display, config, + EGL10.EGL_RED_SIZE, 0); + int g = findConfigAttrib(egl, display, config, + EGL10.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(egl, display, config, + EGL10.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(egl, display, config, + EGL10.EGL_ALPHA_SIZE, 0); + int d = findConfigAttrib(egl, display, config, + EGL10.EGL_DEPTH_SIZE, 0); + int s = findConfigAttrib(egl, display, config, + EGL10.EGL_STENCIL_SIZE, 0); + int rendertype = findConfigAttrib(egl, display, config, + EGL10.EGL_RENDERABLE_TYPE, 0); + int desiredtype = mIsGles3 ? EGL_OPENGL_ES3_BIT : mIsGles2 ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES_BIT; + int nativeRender = findConfigAttrib(egl, display, config, + EGL10.EGL_NATIVE_RENDERABLE, 0); + int caveat = findConfigAttrib(egl, display, config, + EGL10.EGL_CONFIG_CAVEAT, EGL10.EGL_NONE); + int distance = Math.abs(r - mRedSize) + Math.abs(g - mGreenSize) + Math.abs(b - mBlueSize); + int dist1 = distance; + if (mAlphaSize - a > 0) + distance += mAlphaSize - a; + else if (mAlphaSize - a < 0) + distance += 1; // Small penalty if we don't need alpha channel but it is present + int dist2 = distance; + if ((d > 0) != (mDepthSize > 0)) + distance += (mDepthSize > 0) ? 5 : 1; // Small penalty if we don't need zbuffer but it is present + int dist3 = distance; + if ((s > 0) != (mStencilSize > 0)) + distance += (mStencilSize > 0) ? 5 : 1; // Small penalty if we don't need stencil buffer but it is present + int dist4 = distance; + if ((rendertype & desiredtype) == 0) + distance += 5; + int dist5 = distance; + if (caveat == EGL10.EGL_SLOW_CONFIG) + distance += 4; + if (caveat == EGL10.EGL_NON_CONFORMANT_CONFIG) // dunno what that means, probably R and B channels swapped + distance += 1; - private static class SimpleEGLConfigChooser32 extends ComponentSizeChooser { - public SimpleEGLConfigChooser32(boolean withDepthBuffer, boolean stencil, boolean gles2, boolean gles3) { - super(8, 8, 8, 8, withDepthBuffer ? 16 : 0, stencil ? 8 : 0, gles2, gles3); - mRedSize = 8; - mGreenSize = 8; - mBlueSize = 8; - mAlphaSize = 8; - } - } - private static ComponentSizeChooser getEglConfigChooser(int videoDepthBpp, boolean withDepthBuffer, boolean stencil, boolean gles2, boolean gles3) { - if(videoDepthBpp == 16) - return new SimpleEGLConfigChooser16(withDepthBuffer, stencil, gles2, gles3); - if(videoDepthBpp == 24) - return new SimpleEGLConfigChooser24(withDepthBuffer, stencil, gles2, gles3); - if(videoDepthBpp == 32) - return new SimpleEGLConfigChooser32(withDepthBuffer, stencil, gles2, gles3); - return null; - }; + String cfgcur = "R" + r + "G" + g + "B" + b + "A" + a + " depth " + d + " stencil " + s + + " type " + rendertype + " ("; + if ((rendertype & EGL_OPENGL_ES_BIT) != 0) + cfgcur += "GLES"; + if ((rendertype & EGL_OPENGL_ES2_BIT) != 0) + cfgcur += " GLES2"; + if ((rendertype & EGL_OPENGL_ES3_BIT) != 0) + cfgcur += " GLES3"; + if ((rendertype & EGL_OPENGL_BIT) != 0) + cfgcur += " OPENGL"; + if ((rendertype & EGL_OPENVG_BIT) != 0) + cfgcur += " OPENVG"; + cfgcur += ")"; + cfgcur += " caveat " + (caveat == EGL10.EGL_NONE ? "none" : + (caveat == EGL10.EGL_SLOW_CONFIG ? "SLOW" : + caveat == EGL10.EGL_NON_CONFORMANT_CONFIG ? "non-conformant" : + String.valueOf(caveat))); + cfgcur += " nr " + nativeRender; + cfgcur += " pos " + distance + " (" + dist1 + "," + dist2 + "," + dist3 + "," + dist4 + "," + dist5 + ")"; + Log.v("SDL", "GL config " + idx + ": " + cfgcur); + if (distance < closestDistance) { + closestDistance = distance; + closestConfig = config; + cfglog = new String(cfgcur); + selectidx = idx; + } + idx += 1; + } + Log.v("SDL", "GLSurfaceView_SDL::EGLConfigChooser::chooseConfig(): selected " + selectidx + ": " + cfglog); + return closestConfig; + } - /** - * An EGL helper class. - */ + private int findConfigAttrib(EGL10 egl, EGLDisplay display, + EGLConfig config, int attribute, int defaultValue) { + mValue[0] = -1; + if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { + return mValue[0]; + } + Log.w("SDL", "GLSurfaceView_SDL::EGLConfigChooser::findConfigAttrib(): attribute doesn't exist: " + attribute); + return defaultValue; + } - private class EglHelper { - public EglHelper() { + public boolean isGles2Required() { + return mIsGles2; + } - } + public boolean isGles3Required() { + return mIsGles3; + } - /** - * Initialize EGL for a given configuration spec. - * @param configSpec - */ - public void start(){ + private int[] mValue; + // Subclasses can adjust these values: + protected int mRedSize; + protected int mGreenSize; + protected int mBlueSize; + protected int mAlphaSize; + protected int mDepthSize; + protected int mStencilSize; + protected boolean mIsGles2 = false; + protected boolean mIsGles3 = false; - Log.v("SDL", "GLSurfaceView_SDL::EglHelper::start(): creating GL context"); - /* - * Get an EGL instance - */ - mEgl = (EGL10) EGLContext.getEGL(); + public static final int EGL_OPENGL_ES_BIT = 1; + public static final int EGL_OPENVG_BIT = 2; + public static final int EGL_OPENGL_ES2_BIT = 4; + public static final int EGL_OPENGL_BIT = 8; + public static final int EGL_OPENGL_ES3_BIT = 16; + } - /* - * Get to the default display. - */ - mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + /** + * This class will choose a supported surface as close to + * RGB565 as possible, with or without a depth buffer. + */ + private static class SimpleEGLConfigChooser16 extends ComponentSizeChooser { + public SimpleEGLConfigChooser16(boolean withDepthBuffer, boolean stencil, boolean gles2, boolean gles3) { + super(4, 4, 4, 0, withDepthBuffer ? 16 : 0, stencil ? 8 : 0, gles2, gles3); + // Adjust target values. This way we'll accept a 4444 or + // 555 buffer if there's no 565 buffer available. + mRedSize = 5; + mGreenSize = 6; + mBlueSize = 5; + } + } - /* - * We can now initialize EGL for that display - */ - int[] version = new int[2]; - mEgl.eglInitialize(mEglDisplay, version); - mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay); - if( mEglConfig == null ) - Log.e("SDL", "GLSurfaceView_SDL::EglHelper::start(): mEglConfig is NULL"); + private static class SimpleEGLConfigChooser24 extends ComponentSizeChooser { + public SimpleEGLConfigChooser24(boolean withDepthBuffer, boolean stencil, boolean gles2, boolean gles3) { + super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, stencil ? 8 : 0, gles2, gles3); + mRedSize = 8; + mGreenSize = 8; + mBlueSize = 8; + } + } - /* - * Create an OpenGL ES context. This must be done only once, an - * OpenGL context is a somewhat heavy object. - */ - final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - final int[] gles2_attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; - final int[] gles3_attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE }; + private static class SimpleEGLConfigChooser32 extends ComponentSizeChooser { + public SimpleEGLConfigChooser32(boolean withDepthBuffer, boolean stencil, boolean gles2, boolean gles3) { + super(8, 8, 8, 8, withDepthBuffer ? 16 : 0, stencil ? 8 : 0, gles2, gles3); + mRedSize = 8; + mGreenSize = 8; + mBlueSize = 8; + mAlphaSize = 8; + } + } - mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, - EGL10.EGL_NO_CONTEXT, - mEGLConfigChooser.isGles3Required() ? gles3_attrib_list : - mEGLConfigChooser.isGles2Required() ? gles2_attrib_list : null ); + private static ComponentSizeChooser getEglConfigChooser(int videoDepthBpp, boolean withDepthBuffer, boolean stencil, boolean gles2, boolean gles3) { + if (videoDepthBpp == 16) + return new SimpleEGLConfigChooser16(withDepthBuffer, stencil, gles2, gles3); + if (videoDepthBpp == 24) + return new SimpleEGLConfigChooser24(withDepthBuffer, stencil, gles2, gles3); + if (videoDepthBpp == 32) + return new SimpleEGLConfigChooser32(withDepthBuffer, stencil, gles2, gles3); + return null; + } - if( mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT ) - Log.e("SDL", "GLSurfaceView_SDL::EglHelper::start(): mEglContext is EGL_NO_CONTEXT, error: " + mEgl.eglGetError()); + ; - mEglSurface = null; - } + /** + * An EGL helper class. + */ - /* - * React to the creation of a new surface by creating and returning an - * OpenGL interface that renders to that surface. - */ - public GL createSurface(SurfaceHolder holder) { - Log.v("SDL", "GLSurfaceView_SDL::EglHelper::createSurface(): creating GL context"); - /* - * The window size has changed, so we need to create a new - * surface. - */ - if (mEglSurface != null) { + private class EglHelper { + public EglHelper() { - /* - * Unbind and destroy the old EGL surface, if - * there is one. - */ - mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); - mEgl.eglDestroySurface(mEglDisplay, mEglSurface); - } + } - /* - * Create an EGL surface we can render into. - */ + /** + * Initialize EGL for a given configuration spec. + * + * @param configSpec + */ + public void start() { + + Log.v("SDL", "GLSurfaceView_SDL::EglHelper::start(): creating GL context"); + /* + * Get an EGL instance + */ + mEgl = (EGL10) EGLContext.getEGL(); + + /* + * Get to the default display. + */ + mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + /* + * We can now initialize EGL for that display + */ + int[] version = new int[2]; + mEgl.eglInitialize(mEglDisplay, version); + mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay); + if (mEglConfig == null) + Log.e("SDL", "GLSurfaceView_SDL::EglHelper::start(): mEglConfig is NULL"); + + /* + * Create an OpenGL ES context. This must be done only once, an + * OpenGL context is a somewhat heavy object. + */ + final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + final int[] gles2_attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; + final int[] gles3_attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE}; + + mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, + EGL10.EGL_NO_CONTEXT, + mEGLConfigChooser.isGles3Required() ? gles3_attrib_list : + mEGLConfigChooser.isGles2Required() ? gles2_attrib_list : null); + + if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) + Log.e("SDL", "GLSurfaceView_SDL::EglHelper::start(): mEglContext is EGL_NO_CONTEXT, error: " + mEgl.eglGetError()); + + mEglSurface = null; + } + + /* + * React to the creation of a new surface by creating and returning an + * OpenGL interface that renders to that surface. + */ + public GL createSurface(SurfaceHolder holder) { + Log.v("SDL", "GLSurfaceView_SDL::EglHelper::createSurface(): creating GL context"); + /* + * The window size has changed, so we need to create a new + * surface. + */ + if (mEglSurface != null) { + + /* + * Unbind and destroy the old EGL surface, if + * there is one. + */ + mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + mEgl.eglDestroySurface(mEglDisplay, mEglSurface); + } + + /* + * Create an EGL surface we can render into. + */ /* // This does not have any effect on Galaxy Note int [] attribList = new int[4]; @@ -941,110 +950,110 @@ public class GLSurfaceView_SDL extends SurfaceView implements SurfaceHolder.Call attribList[2] = mEgl.EGL_NONE; attribList[3] = mEgl.EGL_NONE; */ - mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, - mEglConfig, holder, null); + mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, + mEglConfig, holder, null); - /* - * Before we can issue GL commands, we need to make sure - * the context is current and bound to a surface. - */ - mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mEglContext); + /* + * Before we can issue GL commands, we need to make sure + * the context is current and bound to a surface. + */ + mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, + mEglContext); - GL gl = mEglContext.getGL(); - if (mGLWrapper != null) { - gl = mGLWrapper.wrap(gl); - } + GL gl = mEglContext.getGL(); + if (mGLWrapper != null) { + gl = mGLWrapper.wrap(gl); + } - return gl; - } + return gl; + } - /** - * Display the current render surface. - * @return false if the context has been lost. - */ - public boolean swap() { - mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); + /** + * Display the current render surface. + * + * @return false if the context has been lost. + */ + public boolean swap() { + mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); - /* - * Always check for EGL_CONTEXT_LOST, which means the context - * and all associated data were lost (For instance because - * the device went to sleep). We need to sleep until we - * get a new surface. - */ - return mEgl.eglGetError() != EGL11.EGL_CONTEXT_LOST; - } + /* + * Always check for EGL_CONTEXT_LOST, which means the context + * and all associated data were lost (For instance because + * the device went to sleep). We need to sleep until we + * get a new surface. + */ + return mEgl.eglGetError() != EGL11.EGL_CONTEXT_LOST; + } - public void finish() { - Log.v("SDL", "GLSurfaceView_SDL::EglHelper::finish(): destroying GL context"); - if (mEglSurface != null) { - mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_CONTEXT); - mEgl.eglDestroySurface(mEglDisplay, mEglSurface); - mEglSurface = null; - } - if (mEglContext != null) { - mEgl.eglDestroyContext(mEglDisplay, mEglContext); - mEglContext = null; - } - if (mEglDisplay != null) { - mEgl.eglTerminate(mEglDisplay); - mEglDisplay = null; - } - } + public void finish() { + Log.v("SDL", "GLSurfaceView_SDL::EglHelper::finish(): destroying GL context"); + if (mEglSurface != null) { + mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_CONTEXT); + mEgl.eglDestroySurface(mEglDisplay, mEglSurface); + mEglSurface = null; + } + if (mEglContext != null) { + mEgl.eglDestroyContext(mEglDisplay, mEglContext); + mEglContext = null; + } + if (mEglDisplay != null) { + mEgl.eglTerminate(mEglDisplay); + mEglDisplay = null; + } + } - EGL10 mEgl; - EGLDisplay mEglDisplay; - EGLSurface mEglSurface; - EGLConfig mEglConfig; - EGLContext mEglContext; - } + EGL10 mEgl; + EGLDisplay mEglDisplay; + EGLSurface mEglSurface; + EGLConfig mEglConfig; + EGLContext mEglContext; + } - /** - * A generic GL Thread. Takes care of initializing EGL and GL. Delegates - * to a Renderer instance to do the actual drawing. Can be configured to - * render continuously or on request. - * - */ - class GLThread extends Thread implements SwapBuffersCallback { - GLThread(Renderer renderer) { - super(); - mDone = false; - mWidth = 0; - mHeight = 0; - mRequestRender = true; - mRenderMode = RENDERMODE_CONTINUOUSLY; - mRenderer = renderer; - mRenderer.setSwapBuffersCallback(this); - setName("GLThread"); - } + /** + * A generic GL Thread. Takes care of initializing EGL and GL. Delegates + * to a Renderer instance to do the actual drawing. Can be configured to + * render continuously or on request. + */ + class GLThread extends Thread implements SwapBuffersCallback { + GLThread(Renderer renderer) { + super(); + mDone = false; + mWidth = 0; + mHeight = 0; + mRequestRender = true; + mRenderMode = RENDERMODE_CONTINUOUSLY; + mRenderer = renderer; + mRenderer.setSwapBuffersCallback(this); + setName("GLThread"); + } - @Override - public void run() { - /* - * When the android framework launches a second instance of - * an activity, the new instance's onCreate() method may be - * called before the first instance returns from onDestroy(). - * - * This semaphore ensures that only one instance at a time - * accesses EGL. - */ - try { - sEglSemaphore.acquire(); - } catch (InterruptedException e) { - return; - } + @Override + public void run() { + /* + * When the android framework launches a second instance of + * an activity, the new instance's onCreate() method may be + * called before the first instance returns from onDestroy(). + * + * This semaphore ensures that only one instance at a time + * accesses EGL. + */ + try { + sEglSemaphore.acquire(); + } catch (InterruptedException e) { + return; + } - mEglHelper = new EglHelper(); - // mEglHelper.start(); - mNeedStart = true; - mSizeChanged = true; - SwapBuffers(); + mEglHelper = new EglHelper(); + // mEglHelper.start(); + mNeedStart = true; + mSizeChanged = true; + SwapBuffers(); - mRenderer.onDrawFrame(mGL); + mRenderer.onDrawFrame(mGL); - mEglHelper.finish(); + mEglHelper.finish(); /* synchronized (sGLThreadManager) { @@ -1053,267 +1062,264 @@ public class GLSurfaceView_SDL extends SurfaceView implements SurfaceHolder.Call sGLThreadManager.threadExiting(this); */ - sEglSemaphore.release(); - } + sEglSemaphore.release(); + } - /* You need to call SwapBuffers() after this function */ - public void ResetVideoSurface() { - mResetVideoSurface = true; - } + /* You need to call SwapBuffers() after this function */ + public void ResetVideoSurface() { + mResetVideoSurface = true; + } - public boolean SwapBuffers() { + public boolean SwapBuffers() { - boolean tellRendererSurfaceCreated = false; - boolean tellRendererSurfaceChanged = false; + boolean tellRendererSurfaceCreated = false; + boolean tellRendererSurfaceChanged = false; - while(true) { // Loop until we're re-created GL context and successfully called swap() + while (true) { // Loop until we're re-created GL context and successfully called swap() - int w, h; - boolean changed = false; - synchronized (this) { - if (mPaused) { - mRenderer.onSurfaceDestroyed(); - mEglHelper.finish(); - mNeedStart = true; - if( Globals.NonBlockingSwapBuffers ) - return false; - } - } - while (needToWait()) { - //Log.v("SDL", "GLSurfaceView_SDL::run(): paused"); - synchronized(this) { - try - { - wait(500); - } - catch(InterruptedException e) - { - Log.v("SDL", "GLSurfaceView_SDL::GLThread::SwapBuffers(): Who dared to interrupt my slumber?"); - Thread.interrupted(); // Clear the flag - } - } - } - synchronized (this) { - if (mDone) - return false; - // changed = mSizeChanged; - w = mWidth; - h = mHeight; - mSizeChanged = false; - mRequestRender = false; - } - if (mNeedStart) { - mEglHelper.start(); - tellRendererSurfaceCreated = true; - changed = true; - mNeedStart = false; - } - if (changed) { - mGL = (GL10) mEglHelper.createSurface(getHolder()); - tellRendererSurfaceChanged = true; - } - if (tellRendererSurfaceCreated) { - mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig); - tellRendererSurfaceCreated = false; - } - if (tellRendererSurfaceChanged) { - mRenderer.onSurfaceChanged(mGL, w, h); - tellRendererSurfaceChanged = false; - } - /* - * Once we're done with GL, we need to call swapBuffers() - * to instruct the system to display the rendered frame - */ - if( !mResetVideoSurface && mEglHelper.swap() ) - return true; - // We've lost GL context - recreate it - mResetVideoSurface = false; - mRenderer.onSurfaceDestroyed(); - mEglHelper.finish(); - mNeedStart = true; - if( Globals.NonBlockingSwapBuffers ) - return false; - } - } + int w, h; + boolean changed = false; + synchronized (this) { + if (mPaused) { + mRenderer.onSurfaceDestroyed(); + mEglHelper.finish(); + mNeedStart = true; + if (Globals.NonBlockingSwapBuffers) + return false; + } + } + while (needToWait()) { + //Log.v("SDL", "GLSurfaceView_SDL::run(): paused"); + synchronized (this) { + try { + wait(500); + } catch (InterruptedException e) { + Log.v("SDL", "GLSurfaceView_SDL::GLThread::SwapBuffers(): Who dared to interrupt my slumber?"); + Thread.interrupted(); // Clear the flag + } + } + } + synchronized (this) { + if (mDone) + return false; + // changed = mSizeChanged; + w = mWidth; + h = mHeight; + mSizeChanged = false; + mRequestRender = false; + } + if (mNeedStart) { + mEglHelper.start(); + tellRendererSurfaceCreated = true; + changed = true; + mNeedStart = false; + } + if (changed) { + mGL = (GL10) mEglHelper.createSurface(getHolder()); + tellRendererSurfaceChanged = true; + } + if (tellRendererSurfaceCreated) { + mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig); + tellRendererSurfaceCreated = false; + } + if (tellRendererSurfaceChanged) { + mRenderer.onSurfaceChanged(mGL, w, h); + tellRendererSurfaceChanged = false; + } + /* + * Once we're done with GL, we need to call swapBuffers() + * to instruct the system to display the rendered frame + */ + if (!mResetVideoSurface && mEglHelper.swap()) + return true; + // We've lost GL context - recreate it + mResetVideoSurface = false; + mRenderer.onSurfaceDestroyed(); + mEglHelper.finish(); + mNeedStart = true; + if (Globals.NonBlockingSwapBuffers) + return false; + } + } - private boolean needToWait() { - if (mKeyguardManager.inKeyguardRestrictedInputMode()) { - return true; // We're in lockscreen - sleep until user unlocks the device - } + private boolean needToWait() { + if (mKeyguardManager.inKeyguardRestrictedInputMode()) { + return true; // We're in lockscreen - sleep until user unlocks the device + } - synchronized (this) { - if (mDone) { - return false; - } + synchronized (this) { + if (mDone) { + return false; + } - if ( Globals.HorizontalOrientation != (mWidth > mHeight) ) - return true; // Wait until screen orientation changes + if (Globals.HorizontalOrientation != (mWidth > mHeight)) + return true; // Wait until screen orientation changes - if (mPaused || (! mHasSurface)) { - return true; - } + if (mPaused || (!mHasSurface)) { + return true; + } - if ((mWidth > 0) && (mHeight > 0) && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY))) { - return false; - } - } + if ((mWidth > 0) && (mHeight > 0) && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY))) { + return false; + } + } - return true; - } + return true; + } - public void setRenderMode(int renderMode) { - if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) { - throw new IllegalArgumentException("renderMode"); - } - synchronized(this) { - mRenderMode = renderMode; - if (renderMode == RENDERMODE_CONTINUOUSLY) { - notify(); - } - } - } + public void setRenderMode(int renderMode) { + if (!((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY))) { + throw new IllegalArgumentException("renderMode"); + } + synchronized (this) { + mRenderMode = renderMode; + if (renderMode == RENDERMODE_CONTINUOUSLY) { + notify(); + } + } + } - public int getRenderMode() { - synchronized(this) { - return mRenderMode; - } - } + public int getRenderMode() { + synchronized (this) { + return mRenderMode; + } + } - public void requestRender() { - synchronized(this) { - mRequestRender = true; - notify(); - } - } + public void requestRender() { + synchronized (this) { + mRequestRender = true; + notify(); + } + } - public void surfaceCreated() { - synchronized(this) { - mHasSurface = true; - notify(); - } - } + public void surfaceCreated() { + synchronized (this) { + mHasSurface = true; + notify(); + } + } - public void surfaceDestroyed() { - synchronized(this) { - mHasSurface = false; - notify(); - } - } + public void surfaceDestroyed() { + synchronized (this) { + mHasSurface = false; + notify(); + } + } - public void onPause() { - Log.v("SDL", "GLSurfaceView_SDL::onPause()"); - synchronized (this) { - mPaused = true; - } - } + public void onPause() { + Log.v("SDL", "GLSurfaceView_SDL::onPause()"); + synchronized (this) { + mPaused = true; + } + } - public void onResume() { - Log.v("SDL", "GLSurfaceView_SDL::onResume()"); - synchronized (this) { - mPaused = false; - notify(); - } - } + public void onResume() { + Log.v("SDL", "GLSurfaceView_SDL::onResume()"); + synchronized (this) { + mPaused = false; + notify(); + } + } - public void onWindowResize(int w, int h) { - Log.v("SDL", "GLSurfaceView_SDL::onWindowResize(): " + w + "x" + h); - synchronized (this) { - mWidth = w; - mHeight = h; - mSizeChanged = true; - mRenderer.onWindowResize(w, h); - notify(); - } - } + public void onWindowResize(int w, int h) { + Log.v("SDL", "GLSurfaceView_SDL::onWindowResize(): " + w + "x" + h); + synchronized (this) { + mWidth = w; + mHeight = h; + mSizeChanged = true; + mRenderer.onWindowResize(w, h); + notify(); + } + } - public void requestExitAndWait() { - // don't call this from GLThread thread or it is a guaranteed - // deadlock! - Log.v("SDL", "GLSurfaceView_SDL::requestExitAndWait()"); - synchronized(this) { - mDone = true; - notify(); - } - try { - join(); - } catch (InterruptedException ex) { - //Thread.currentThread().interrupt(); - } - } + public void requestExitAndWait() { + // don't call this from GLThread thread or it is a guaranteed + // deadlock! + Log.v("SDL", "GLSurfaceView_SDL::requestExitAndWait()"); + synchronized (this) { + mDone = true; + notify(); + } + try { + join(); + } catch (InterruptedException ex) { + //Thread.currentThread().interrupt(); + } + } - /** - * Queue an "event" to be run on the GL rendering thread. - * @param r the runnable to be run on the GL rendering thread. - */ - public void queueEvent(Runnable r) { - synchronized(this) { - mEventQueue.add(r); - } - } + /** + * Queue an "event" to be run on the GL rendering thread. + * + * @param r the runnable to be run on the GL rendering thread. + */ + public void queueEvent(Runnable r) { + synchronized (this) { + mEventQueue.add(r); + } + } - private Runnable getEvent() { - synchronized(this) { - if (mEventQueue.size() > 0) { - return mEventQueue.remove(0); - } + private Runnable getEvent() { + synchronized (this) { + if (mEventQueue.size() > 0) { + return mEventQueue.remove(0); + } - } - return null; - } + } + return null; + } - private boolean mDone; - private boolean mPaused; - private boolean mHasSurface; - private int mWidth; - private int mHeight; - private int mRenderMode; - private boolean mRequestRender; - private Renderer mRenderer; - private ArrayList mEventQueue = new ArrayList(); - private EglHelper mEglHelper; - private GL10 mGL = null; - private boolean mNeedStart = false; - private boolean mResetVideoSurface = false; - } + private boolean mDone; + private boolean mPaused; + private boolean mHasSurface; + private int mWidth; + private int mHeight; + private int mRenderMode; + private boolean mRequestRender; + private Renderer mRenderer; + private ArrayList mEventQueue = new ArrayList(); + private EglHelper mEglHelper; + private GL10 mGL = null; + private boolean mNeedStart = false; + private boolean mResetVideoSurface = false; + } - static class LogWriter extends Writer { + static class LogWriter extends Writer { - @Override public void close() { - flushBuilder(); - } + @Override public void close() { + flushBuilder(); + } - @Override public void flush() { - flushBuilder(); - } + @Override public void flush() { + flushBuilder(); + } - @Override public void write(char[] buf, int offset, int count) { - for(int i = 0; i < count; i++) { - char c = buf[offset + i]; - if ( c == '\n') { - flushBuilder(); - } - else { - mBuilder.append(c); - } - } - } + @Override public void write(char[] buf, int offset, int count) { + for (int i = 0; i < count; i++) { + char c = buf[offset + i]; + if (c == '\n') { + flushBuilder(); + } else { + mBuilder.append(c); + } + } + } - private void flushBuilder() { - if (mBuilder.length() > 0) { - Log.v("GLSurfaceView", mBuilder.toString()); - mBuilder.delete(0, mBuilder.length()); - } - } + private void flushBuilder() { + if (mBuilder.length() > 0) { + Log.v("GLSurfaceView", mBuilder.toString()); + mBuilder.delete(0, mBuilder.length()); + } + } - private StringBuilder mBuilder = new StringBuilder(); - } + private StringBuilder mBuilder = new StringBuilder(); + } - private static final Semaphore sEglSemaphore = new Semaphore(1); - private boolean mSizeChanged = true; + private static final Semaphore sEglSemaphore = new Semaphore(1); + private boolean mSizeChanged = true; - private GLThread mGLThread; - private EGLConfigChooser mEGLConfigChooser; - private GLWrapper mGLWrapper; - private int mDebugFlags; - private KeyguardManager mKeyguardManager; + private GLThread mGLThread; + private EGLConfigChooser mEGLConfigChooser; + private GLWrapper mGLWrapper; + private int mDebugFlags; + private KeyguardManager mKeyguardManager; } diff --git a/Xorg/src/main/java/io/neoterm/Globals.java b/Xorg/src/main/java/io/neoterm/Globals.java index 260ae44..feacafa 100644 --- a/Xorg/src/main/java/io/neoterm/Globals.java +++ b/Xorg/src/main/java/io/neoterm/Globals.java @@ -25,124 +25,124 @@ package io.neoterm; import android.view.KeyEvent; public class Globals { - public static String XLIB_DIR = "/data/data/io.neoterm/files/usr/lib/xorg-neoterm"; - public static String XLIBS[] = { - "x11_sdl_native_helpers", - "x11_sdl-1.2", - "x11_sdl_ttf", - "x11_crypto", - }; - public static String XAPP_LIBS[] = { - "x11_application", - "x11_sdl_main", - }; + public static String XLIB_DIR = "/data/data/io.neoterm/files/usr/lib/xorg-neoterm"; + public static String XLIBS[] = { + "x11_sdl_native_helpers", + "x11_sdl-1.2", + "x11_sdl_ttf", + "x11_crypto", + }; + public static String XAPP_LIBS[] = { + "x11_application", + "x11_sdl_main", + }; - // These config options are modified by ChangeAppsettings.sh script - see the detailed descriptions there - public static String AppLibraries[] = {"sdl_native_helpers", "sdl-1.2", "sdl_ttf", "crypto"}; - public static final boolean Using_SDL_1_3 = false; - public static final boolean Using_SDL_2_0 = false; - public static String[] DataDownloadUrl = {"!!Data files|:data.tar.gz:data-1.tgz", "!!Data files|:DroidSansMono.ttf:DroidSansMono.ttf", "Additional fonts (90Mb)|:xfonts.tar.gz:http://sourceforge.net/projects/libsdl-android/files/apk/XServer-XSDL/xfonts.tgz/download",}; - public static boolean SwVideoMode = true; - public static boolean NeedDepthBuffer = false; - public static boolean NeedStencilBuffer = false; - public static boolean NeedGles2 = false; - public static boolean NeedGles3 = false; - public static boolean CompatibilityHacksVideo = false; - public static boolean CompatibilityHacksForceScreenUpdateMouseClick = true; - public static boolean CompatibilityHacksStaticInit = false; - public static boolean CompatibilityHacksTextInputEmulatesHwKeyboard = true; - public static int TextInputKeyboard = 0; - public static boolean KeepAspectRatioDefaultSetting = false; - public static boolean InhibitSuspend = true; - public static boolean CreateService = true; - public static String ReadmeText = ""; - public static String CommandLine = "XSDL"; - public static boolean AppUsesMouse = true; - public static boolean AppNeedsTwoButtonMouse = true; - public static boolean RightMouseButtonLongPress = false; - public static boolean ForceRelativeMouseMode = true; // If both on-screen keyboard and mouse are needed, this will only set the default setting, user may override it later - public static boolean ShowMouseCursor = false; // Draw system mouse cursor, if the app does not do it - public static boolean ScreenFollowsMouse = true; // Move app screen make mouse cursor always visible, when soft keyboard is shown - public static boolean AppNeedsArrowKeys = false; - public static boolean AppNeedsTextInput = false; - public static boolean AppUsesJoystick = false; - public static boolean AppUsesSecondJoystick = false; - public static boolean AppUsesThirdJoystick = false; - public static boolean AppUsesAccelerometer = false; - public static boolean AppUsesGyroscope = false; - public static boolean AppUsesOrientationSensor = false; - public static boolean AppUsesMultitouch = true; - public static boolean NonBlockingSwapBuffers = false; - public static boolean ResetSdlConfigForThisVersion = false; - public static String DeleteFilesOnUpgrade = "%"; - public static int AppTouchscreenKeyboardKeysAmount = 3; - public static String[] AppTouchscreenKeyboardKeysNames = "LCTRL LALT LSHIFT RETURN SPACE DELETE KP_PLUS KP_MINUS 1 2".split(" "); - public static int StartupMenuButtonTimeout = 3000; - public static int AppMinimumRAM = 0; - public static SettingsMenu.Menu HiddenMenuOptions[] = {}; // If you see error here - update HiddenMenuOptions in your AndroidAppSettings.cfg: change OptionalDownloadConfig to SettingsMenuMisc.OptionalDownloadConfig etc. - public static SettingsMenu.Menu FirstStartMenuOptions[] = {new SettingsMenuMisc.GyroscopeCalibration(), new SettingsMenuMisc.OptionalDownloadConfig(),}; + // These config options are modified by ChangeAppsettings.sh script - see the detailed descriptions there + public static String AppLibraries[] = {"sdl_native_helpers", "sdl-1.2", "sdl_ttf", "crypto"}; + public static final boolean Using_SDL_1_3 = false; + public static final boolean Using_SDL_2_0 = false; + public static String[] DataDownloadUrl = {"!!Data files|:data.tar.gz:data-1.tgz", "!!Data files|:DroidSansMono.ttf:DroidSansMono.ttf", "Additional fonts (90Mb)|:xfonts.tar.gz:http://sourceforge.net/projects/libsdl-android/files/apk/XServer-XSDL/xfonts.tgz/download",}; + public static boolean SwVideoMode = true; + public static boolean NeedDepthBuffer = false; + public static boolean NeedStencilBuffer = false; + public static boolean NeedGles2 = false; + public static boolean NeedGles3 = false; + public static boolean CompatibilityHacksVideo = false; + public static boolean CompatibilityHacksForceScreenUpdateMouseClick = true; + public static boolean CompatibilityHacksStaticInit = false; + public static boolean CompatibilityHacksTextInputEmulatesHwKeyboard = true; + public static int TextInputKeyboard = 0; + public static boolean KeepAspectRatioDefaultSetting = false; + public static boolean InhibitSuspend = true; + public static boolean CreateService = true; + public static String ReadmeText = ""; + public static String CommandLine = "XSDL"; + public static boolean AppUsesMouse = true; + public static boolean AppNeedsTwoButtonMouse = true; + public static boolean RightMouseButtonLongPress = false; + public static boolean ForceRelativeMouseMode = true; // If both on-screen keyboard and mouse are needed, this will only set the default setting, user may override it later + public static boolean ShowMouseCursor = false; // Draw system mouse cursor, if the app does not do it + public static boolean ScreenFollowsMouse = true; // Move app screen make mouse cursor always visible, when soft keyboard is shown + public static boolean AppNeedsArrowKeys = false; + public static boolean AppNeedsTextInput = false; + public static boolean AppUsesJoystick = false; + public static boolean AppUsesSecondJoystick = false; + public static boolean AppUsesThirdJoystick = false; + public static boolean AppUsesAccelerometer = false; + public static boolean AppUsesGyroscope = false; + public static boolean AppUsesOrientationSensor = false; + public static boolean AppUsesMultitouch = true; + public static boolean NonBlockingSwapBuffers = false; + public static boolean ResetSdlConfigForThisVersion = false; + public static String DeleteFilesOnUpgrade = "%"; + public static int AppTouchscreenKeyboardKeysAmount = 3; + public static String[] AppTouchscreenKeyboardKeysNames = "LCTRL LALT LSHIFT RETURN SPACE DELETE KP_PLUS KP_MINUS 1 2".split(" "); + public static int StartupMenuButtonTimeout = 3000; + public static int AppMinimumRAM = 0; + public static SettingsMenu.Menu HiddenMenuOptions[] = {}; // If you see error here - update HiddenMenuOptions in your AndroidAppSettings.cfg: change OptionalDownloadConfig to SettingsMenuMisc.OptionalDownloadConfig etc. + public static SettingsMenu.Menu FirstStartMenuOptions[] = {new SettingsMenuMisc.GyroscopeCalibration(), new SettingsMenuMisc.OptionalDownloadConfig(),}; - // Phone-specific config, modified by user in "Change phone config" startup dialog - public static int VideoDepthBpp = 16; - public static boolean HorizontalOrientation = true; - public static boolean AutoDetectOrientation = false; - public static boolean ImmersiveMode = true; - public static boolean HideSystemMousePointer = false; - public static boolean DownloadToSdcard = true; - public static boolean PhoneHasArrowKeys = false; - public static boolean UseAccelerometerAsArrowKeys = false; - public static boolean UseTouchscreenKeyboard = true; - public static int TouchscreenKeyboardSize = 1; - public static final int TOUCHSCREEN_KEYBOARD_CUSTOM = 4; - public static int TouchscreenKeyboardDrawSize = 2; - public static int TouchscreenKeyboardTheme = 0; - public static int TouchscreenKeyboardTransparency = 2; - public static boolean FloatingScreenJoystick = false; - public static int AccelerometerSensitivity = 2; - public static int AccelerometerCenterPos = 2; - public static int AudioBufferConfig = 0; - public static boolean OptionalDataDownload[] = null; - public static int LeftClickMethod = ForceRelativeMouseMode ? Mouse.LEFT_CLICK_WITH_TAP_OR_TIMEOUT : Mouse.LEFT_CLICK_NORMAL; - public static int LeftClickKey = KeyEvent.KEYCODE_DPAD_CENTER; - public static int LeftClickTimeout = 3; - public static int RightClickTimeout = 4; - public static int RightClickMethod = AppNeedsTwoButtonMouse ? Mouse.RIGHT_CLICK_WITH_MULTITOUCH : Mouse.RIGHT_CLICK_NONE; - public static int RightClickKey = KeyEvent.KEYCODE_MENU; - public static boolean MoveMouseWithJoystick = false; - public static int MoveMouseWithJoystickSpeed = 1; - public static int MoveMouseWithJoystickAccel = 0; - public static boolean MoveMouseWithGyroscope = true; - public static int MoveMouseWithGyroscopeSpeed = 2; - public static boolean ClickMouseWithDpad = false; - public static boolean RelativeMouseMovement = ForceRelativeMouseMode; // Laptop touchpad mode - public static boolean ForceHardwareMouse = false; - public static int RelativeMouseMovementSpeed = 2; - public static int RelativeMouseMovementAccel = 0; - public static int ShowScreenUnderFinger = Mouse.ZOOM_NONE; - public static int ClickScreenPressure = 0; - public static int ClickScreenTouchspotSize = 0; - public static boolean FingerHover = true; - public static boolean HoverJitterFilter = true; - public static boolean GenerateSubframeTouchEvents = false; - public static boolean KeepAspectRatio = KeepAspectRatioDefaultSetting; - public static boolean TvBorders = true; - public static int RemapHwKeycode[] = new int[SDL_Keys.JAVA_KEYCODE_LAST]; - public static int RemapScreenKbKeycode[] = new int[6]; - public static int ScreenKbControlsLayout[][] = AppUsesThirdJoystick ? // Values for 800x480 resolution - new int[][]{{0, 303, 177, 480}, {0, 0, 48, 48}, {400, 392, 488, 480}, {312, 392, 400, 480}, {400, 304, 488, 392}, {312, 304, 400, 392}, {400, 216, 488, 304}, {312, 216, 400, 304}, {623, 303, 800, 480}, {623, 126, 800, 303}} : - AppUsesSecondJoystick ? - new int[][]{{0, 303, 177, 480}, {0, 0, 48, 48}, {400, 392, 488, 480}, {312, 392, 400, 480}, {400, 304, 488, 392}, {312, 304, 400, 392}, {400, 216, 488, 304}, {312, 216, 400, 304}, {623, 303, 800, 480}} : - new int[][]{{0, 303, 177, 480}, {0, 0, 48, 48}, {712, 392, 800, 480}, {624, 392, 712, 480}, {712, 304, 800, 392}, {624, 304, 712, 392}, {712, 216, 800, 304}, {624, 216, 712, 304}}; - public static boolean ScreenKbControlsShown[] = new boolean[ScreenKbControlsLayout.length]; /* Also joystick and text input button added */ - public static int RemapMultitouchGestureKeycode[] = new int[4]; - public static boolean MultitouchGesturesUsed[] = new boolean[4]; - public static int MultitouchGestureSensitivity = 1; - public static int TouchscreenCalibration[] = new int[4]; - public static String DataDir = "/data/data/io.neoterm/files/usr/share/xorg-neoterm"; - public static String UnSecureDataDir = "/data/data/io.neoterm/files/usr/share/xorg-neoterm"; - public static String HomeDir = "/data/data/io.neoterm/files/home"; - public static boolean VideoLinearFilter = true; - public static boolean MultiThreadedVideo = false; + // Phone-specific config, modified by user in "Change phone config" startup dialog + public static int VideoDepthBpp = 16; + public static boolean HorizontalOrientation = true; + public static boolean AutoDetectOrientation = false; + public static boolean ImmersiveMode = true; + public static boolean HideSystemMousePointer = false; + public static boolean DownloadToSdcard = true; + public static boolean PhoneHasArrowKeys = false; + public static boolean UseAccelerometerAsArrowKeys = false; + public static boolean UseTouchscreenKeyboard = true; + public static int TouchscreenKeyboardSize = 1; + public static final int TOUCHSCREEN_KEYBOARD_CUSTOM = 4; + public static int TouchscreenKeyboardDrawSize = 2; + public static int TouchscreenKeyboardTheme = 0; + public static int TouchscreenKeyboardTransparency = 2; + public static boolean FloatingScreenJoystick = false; + public static int AccelerometerSensitivity = 2; + public static int AccelerometerCenterPos = 2; + public static int AudioBufferConfig = 0; + public static boolean OptionalDataDownload[] = null; + public static int LeftClickMethod = ForceRelativeMouseMode ? Mouse.LEFT_CLICK_WITH_TAP_OR_TIMEOUT : Mouse.LEFT_CLICK_NORMAL; + public static int LeftClickKey = KeyEvent.KEYCODE_DPAD_CENTER; + public static int LeftClickTimeout = 3; + public static int RightClickTimeout = 4; + public static int RightClickMethod = AppNeedsTwoButtonMouse ? Mouse.RIGHT_CLICK_WITH_MULTITOUCH : Mouse.RIGHT_CLICK_NONE; + public static int RightClickKey = KeyEvent.KEYCODE_MENU; + public static boolean MoveMouseWithJoystick = false; + public static int MoveMouseWithJoystickSpeed = 1; + public static int MoveMouseWithJoystickAccel = 0; + public static boolean MoveMouseWithGyroscope = true; + public static int MoveMouseWithGyroscopeSpeed = 2; + public static boolean ClickMouseWithDpad = false; + public static boolean RelativeMouseMovement = ForceRelativeMouseMode; // Laptop touchpad mode + public static boolean ForceHardwareMouse = false; + public static int RelativeMouseMovementSpeed = 2; + public static int RelativeMouseMovementAccel = 0; + public static int ShowScreenUnderFinger = Mouse.ZOOM_NONE; + public static int ClickScreenPressure = 0; + public static int ClickScreenTouchspotSize = 0; + public static boolean FingerHover = true; + public static boolean HoverJitterFilter = true; + public static boolean GenerateSubframeTouchEvents = false; + public static boolean KeepAspectRatio = KeepAspectRatioDefaultSetting; + public static boolean TvBorders = true; + public static int RemapHwKeycode[] = new int[SDL_Keys.JAVA_KEYCODE_LAST]; + public static int RemapScreenKbKeycode[] = new int[6]; + public static int ScreenKbControlsLayout[][] = AppUsesThirdJoystick ? // Values for 800x480 resolution + new int[][]{{0, 303, 177, 480}, {0, 0, 48, 48}, {400, 392, 488, 480}, {312, 392, 400, 480}, {400, 304, 488, 392}, {312, 304, 400, 392}, {400, 216, 488, 304}, {312, 216, 400, 304}, {623, 303, 800, 480}, {623, 126, 800, 303}} : + AppUsesSecondJoystick ? + new int[][]{{0, 303, 177, 480}, {0, 0, 48, 48}, {400, 392, 488, 480}, {312, 392, 400, 480}, {400, 304, 488, 392}, {312, 304, 400, 392}, {400, 216, 488, 304}, {312, 216, 400, 304}, {623, 303, 800, 480}} : + new int[][]{{0, 303, 177, 480}, {0, 0, 48, 48}, {712, 392, 800, 480}, {624, 392, 712, 480}, {712, 304, 800, 392}, {624, 304, 712, 392}, {712, 216, 800, 304}, {624, 216, 712, 304}}; + public static boolean ScreenKbControlsShown[] = new boolean[ScreenKbControlsLayout.length]; /* Also joystick and text input button added */ + public static int RemapMultitouchGestureKeycode[] = new int[4]; + public static boolean MultitouchGesturesUsed[] = new boolean[4]; + public static int MultitouchGestureSensitivity = 1; + public static int TouchscreenCalibration[] = new int[4]; + public static String DataDir = "/data/data/io.neoterm/files/usr/share/xorg-neoterm"; + public static String UnSecureDataDir = "/data/data/io.neoterm/files/usr/share/xorg-neoterm"; + public static String HomeDir = "/data/data/io.neoterm/files/home"; + public static boolean VideoLinearFilter = true; + public static boolean MultiThreadedVideo = false; - public static boolean OuyaEmulation = false; // For debugging + public static boolean OuyaEmulation = false; // For debugging } diff --git a/Xorg/src/main/java/io/neoterm/Keycodes.java b/Xorg/src/main/java/io/neoterm/Keycodes.java index 53c4259..7b47cfd 100644 --- a/Xorg/src/main/java/io/neoterm/Keycodes.java +++ b/Xorg/src/main/java/io/neoterm/Keycodes.java @@ -22,594 +22,580 @@ freely, subject to the following restrictions: package io.neoterm; -import java.lang.String; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; -import java.lang.reflect.Field; // Autogenerated by hand with a command: // grep 'SDLK_' SDL_keysym.h | sed 's/SDLK_\([a-zA-Z0-9_]\+\).*[=] \([0-9]\+\).*/public static final int SDLK_\1 = \2;/' >> Keycodes.java -class SDL_1_2_Keycodes -{ - public static final int SDLK_UNKNOWN = 0; - public static final int SDLK_BACKSPACE = 8; - public static final int SDLK_TAB = 9; - public static final int SDLK_CLEAR = 12; - public static final int SDLK_RETURN = 13; - public static final int SDLK_PAUSE = 19; - public static final int SDLK_ESCAPE = 27; - public static final int SDLK_SPACE = 32; - public static final int SDLK_EXCLAIM = 33; - public static final int SDLK_QUOTEDBL = 34; - public static final int SDLK_HASH = 35; - public static final int SDLK_DOLLAR = 36; - public static final int SDLK_AMPERSAND = 38; - public static final int SDLK_QUOTE = 39; - public static final int SDLK_LEFTPAREN = 40; - public static final int SDLK_RIGHTPAREN = 41; - public static final int SDLK_ASTERISK = 42; - public static final int SDLK_PLUS = 43; - public static final int SDLK_COMMA = 44; - public static final int SDLK_MINUS = 45; - public static final int SDLK_PERIOD = 46; - public static final int SDLK_SLASH = 47; - public static final int SDLK_0 = 48; - public static final int SDLK_1 = 49; - public static final int SDLK_2 = 50; - public static final int SDLK_3 = 51; - public static final int SDLK_4 = 52; - public static final int SDLK_5 = 53; - public static final int SDLK_6 = 54; - public static final int SDLK_7 = 55; - public static final int SDLK_8 = 56; - public static final int SDLK_9 = 57; - public static final int SDLK_COLON = 58; - public static final int SDLK_SEMICOLON = 59; - public static final int SDLK_LESS = 60; - public static final int SDLK_EQUALS = 61; - public static final int SDLK_GREATER = 62; - public static final int SDLK_QUESTION = 63; - public static final int SDLK_AT = 64; - public static final int SDLK_LEFTBRACKET = 91; - public static final int SDLK_BACKSLASH = 92; - public static final int SDLK_RIGHTBRACKET = 93; - public static final int SDLK_CARET = 94; - public static final int SDLK_UNDERSCORE = 95; - public static final int SDLK_BACKQUOTE = 96; - public static final int SDLK_a = 97; - public static final int SDLK_b = 98; - public static final int SDLK_c = 99; - public static final int SDLK_d = 100; - public static final int SDLK_e = 101; - public static final int SDLK_f = 102; - public static final int SDLK_g = 103; - public static final int SDLK_h = 104; - public static final int SDLK_i = 105; - public static final int SDLK_j = 106; - public static final int SDLK_k = 107; - public static final int SDLK_l = 108; - public static final int SDLK_m = 109; - public static final int SDLK_n = 110; - public static final int SDLK_o = 111; - public static final int SDLK_p = 112; - public static final int SDLK_q = 113; - public static final int SDLK_r = 114; - public static final int SDLK_s = 115; - public static final int SDLK_t = 116; - public static final int SDLK_u = 117; - public static final int SDLK_v = 118; - public static final int SDLK_w = 119; - public static final int SDLK_x = 120; - public static final int SDLK_y = 121; - public static final int SDLK_z = 122; - public static final int SDLK_DELETE = 127; - public static final int SDLK_WORLD_0 = 160; - public static final int SDLK_WORLD_1 = 161; - public static final int SDLK_WORLD_2 = 162; - public static final int SDLK_WORLD_3 = 163; - public static final int SDLK_WORLD_4 = 164; - public static final int SDLK_WORLD_5 = 165; - public static final int SDLK_WORLD_6 = 166; - public static final int SDLK_WORLD_7 = 167; - public static final int SDLK_WORLD_8 = 168; - public static final int SDLK_WORLD_9 = 169; - public static final int SDLK_WORLD_10 = 170; - public static final int SDLK_WORLD_11 = 171; - public static final int SDLK_WORLD_12 = 172; - public static final int SDLK_WORLD_13 = 173; - public static final int SDLK_WORLD_14 = 174; - public static final int SDLK_WORLD_15 = 175; - public static final int SDLK_WORLD_16 = 176; - public static final int SDLK_WORLD_17 = 177; - public static final int SDLK_WORLD_18 = 178; - public static final int SDLK_WORLD_19 = 179; - public static final int SDLK_WORLD_20 = 180; - public static final int SDLK_WORLD_21 = 181; - public static final int SDLK_WORLD_22 = 182; - public static final int SDLK_WORLD_23 = 183; - public static final int SDLK_WORLD_24 = 184; - public static final int SDLK_WORLD_25 = 185; - public static final int SDLK_WORLD_26 = 186; - public static final int SDLK_WORLD_27 = 187; - public static final int SDLK_WORLD_28 = 188; - public static final int SDLK_WORLD_29 = 189; - public static final int SDLK_WORLD_30 = 190; - public static final int SDLK_WORLD_31 = 191; - public static final int SDLK_WORLD_32 = 192; - public static final int SDLK_WORLD_33 = 193; - public static final int SDLK_WORLD_34 = 194; - public static final int SDLK_WORLD_35 = 195; - public static final int SDLK_WORLD_36 = 196; - public static final int SDLK_WORLD_37 = 197; - public static final int SDLK_WORLD_38 = 198; - public static final int SDLK_WORLD_39 = 199; - public static final int SDLK_WORLD_40 = 200; - public static final int SDLK_WORLD_41 = 201; - public static final int SDLK_WORLD_42 = 202; - public static final int SDLK_WORLD_43 = 203; - public static final int SDLK_WORLD_44 = 204; - public static final int SDLK_WORLD_45 = 205; - public static final int SDLK_WORLD_46 = 206; - public static final int SDLK_WORLD_47 = 207; - public static final int SDLK_WORLD_48 = 208; - public static final int SDLK_WORLD_49 = 209; - public static final int SDLK_WORLD_50 = 210; - public static final int SDLK_WORLD_51 = 211; - public static final int SDLK_WORLD_52 = 212; - public static final int SDLK_WORLD_53 = 213; - public static final int SDLK_WORLD_54 = 214; - public static final int SDLK_WORLD_55 = 215; - public static final int SDLK_WORLD_56 = 216; - public static final int SDLK_WORLD_57 = 217; - public static final int SDLK_WORLD_58 = 218; - public static final int SDLK_WORLD_59 = 219; - public static final int SDLK_WORLD_60 = 220; - public static final int SDLK_WORLD_61 = 221; - public static final int SDLK_WORLD_62 = 222; - public static final int SDLK_WORLD_63 = 223; - public static final int SDLK_WORLD_64 = 224; - public static final int SDLK_WORLD_65 = 225; - public static final int SDLK_WORLD_66 = 226; - public static final int SDLK_WORLD_67 = 227; - public static final int SDLK_WORLD_68 = 228; - public static final int SDLK_WORLD_69 = 229; - public static final int SDLK_WORLD_70 = 230; - public static final int SDLK_WORLD_71 = 231; - public static final int SDLK_WORLD_72 = 232; - public static final int SDLK_WORLD_73 = 233; - public static final int SDLK_WORLD_74 = 234; - public static final int SDLK_WORLD_75 = 235; - public static final int SDLK_WORLD_76 = 236; - public static final int SDLK_WORLD_77 = 237; - public static final int SDLK_WORLD_78 = 238; - public static final int SDLK_WORLD_79 = 239; - public static final int SDLK_WORLD_80 = 240; - public static final int SDLK_WORLD_81 = 241; - public static final int SDLK_WORLD_82 = 242; - public static final int SDLK_WORLD_83 = 243; - public static final int SDLK_WORLD_84 = 244; - public static final int SDLK_WORLD_85 = 245; - public static final int SDLK_WORLD_86 = 246; - public static final int SDLK_WORLD_87 = 247; - public static final int SDLK_WORLD_88 = 248; - public static final int SDLK_WORLD_89 = 249; - public static final int SDLK_WORLD_90 = 250; - public static final int SDLK_WORLD_91 = 251; - public static final int SDLK_WORLD_92 = 252; - public static final int SDLK_WORLD_93 = 253; - public static final int SDLK_WORLD_94 = 254; - public static final int SDLK_WORLD_95 = 255; - public static final int SDLK_KP0 = 256; - public static final int SDLK_KP1 = 257; - public static final int SDLK_KP2 = 258; - public static final int SDLK_KP3 = 259; - public static final int SDLK_KP4 = 260; - public static final int SDLK_KP5 = 261; - public static final int SDLK_KP6 = 262; - public static final int SDLK_KP7 = 263; - public static final int SDLK_KP8 = 264; - public static final int SDLK_KP9 = 265; - public static final int SDLK_KP_PERIOD = 266; - public static final int SDLK_KP_DIVIDE = 267; - public static final int SDLK_KP_MULTIPLY = 268; - public static final int SDLK_KP_MINUS = 269; - public static final int SDLK_KP_PLUS = 270; - public static final int SDLK_KP_ENTER = 271; - public static final int SDLK_KP_EQUALS = 272; - public static final int SDLK_UP = 273; - public static final int SDLK_DOWN = 274; - public static final int SDLK_RIGHT = 275; - public static final int SDLK_LEFT = 276; - public static final int SDLK_INSERT = 277; - public static final int SDLK_HOME = 278; - public static final int SDLK_END = 279; - public static final int SDLK_PAGEUP = 280; - public static final int SDLK_PAGEDOWN = 281; - public static final int SDLK_F1 = 282; - public static final int SDLK_F2 = 283; - public static final int SDLK_F3 = 284; - public static final int SDLK_F4 = 285; - public static final int SDLK_F5 = 286; - public static final int SDLK_F6 = 287; - public static final int SDLK_F7 = 288; - public static final int SDLK_F8 = 289; - public static final int SDLK_F9 = 290; - public static final int SDLK_F10 = 291; - public static final int SDLK_F11 = 292; - public static final int SDLK_F12 = 293; - public static final int SDLK_F13 = 294; - public static final int SDLK_F14 = 295; - public static final int SDLK_F15 = 296; - public static final int SDLK_NUMLOCK = 300; - public static final int SDLK_CAPSLOCK = 301; - public static final int SDLK_SCROLLOCK = 302; - public static final int SDLK_RSHIFT = 303; - public static final int SDLK_LSHIFT = 304; - public static final int SDLK_RCTRL = 305; - public static final int SDLK_LCTRL = 306; - public static final int SDLK_RALT = 307; - public static final int SDLK_LALT = 308; - public static final int SDLK_RMETA = 309; - public static final int SDLK_LMETA = 310; - public static final int SDLK_LSUPER = 311; - public static final int SDLK_RSUPER = 312; - public static final int SDLK_MODE = 313; - public static final int SDLK_COMPOSE = 314; - public static final int SDLK_HELP = 315; - public static final int SDLK_PRINT = 316; - public static final int SDLK_SYSREQ = 317; - public static final int SDLK_BREAK = 318; - public static final int SDLK_MENU = 319; - public static final int SDLK_POWER = 320; - public static final int SDLK_EURO = 321; - public static final int SDLK_UNDO = 322; +class SDL_1_2_Keycodes { + public static final int SDLK_UNKNOWN = 0; + public static final int SDLK_BACKSPACE = 8; + public static final int SDLK_TAB = 9; + public static final int SDLK_CLEAR = 12; + public static final int SDLK_RETURN = 13; + public static final int SDLK_PAUSE = 19; + public static final int SDLK_ESCAPE = 27; + public static final int SDLK_SPACE = 32; + public static final int SDLK_EXCLAIM = 33; + public static final int SDLK_QUOTEDBL = 34; + public static final int SDLK_HASH = 35; + public static final int SDLK_DOLLAR = 36; + public static final int SDLK_AMPERSAND = 38; + public static final int SDLK_QUOTE = 39; + public static final int SDLK_LEFTPAREN = 40; + public static final int SDLK_RIGHTPAREN = 41; + public static final int SDLK_ASTERISK = 42; + public static final int SDLK_PLUS = 43; + public static final int SDLK_COMMA = 44; + public static final int SDLK_MINUS = 45; + public static final int SDLK_PERIOD = 46; + public static final int SDLK_SLASH = 47; + public static final int SDLK_0 = 48; + public static final int SDLK_1 = 49; + public static final int SDLK_2 = 50; + public static final int SDLK_3 = 51; + public static final int SDLK_4 = 52; + public static final int SDLK_5 = 53; + public static final int SDLK_6 = 54; + public static final int SDLK_7 = 55; + public static final int SDLK_8 = 56; + public static final int SDLK_9 = 57; + public static final int SDLK_COLON = 58; + public static final int SDLK_SEMICOLON = 59; + public static final int SDLK_LESS = 60; + public static final int SDLK_EQUALS = 61; + public static final int SDLK_GREATER = 62; + public static final int SDLK_QUESTION = 63; + public static final int SDLK_AT = 64; + public static final int SDLK_LEFTBRACKET = 91; + public static final int SDLK_BACKSLASH = 92; + public static final int SDLK_RIGHTBRACKET = 93; + public static final int SDLK_CARET = 94; + public static final int SDLK_UNDERSCORE = 95; + public static final int SDLK_BACKQUOTE = 96; + public static final int SDLK_a = 97; + public static final int SDLK_b = 98; + public static final int SDLK_c = 99; + public static final int SDLK_d = 100; + public static final int SDLK_e = 101; + public static final int SDLK_f = 102; + public static final int SDLK_g = 103; + public static final int SDLK_h = 104; + public static final int SDLK_i = 105; + public static final int SDLK_j = 106; + public static final int SDLK_k = 107; + public static final int SDLK_l = 108; + public static final int SDLK_m = 109; + public static final int SDLK_n = 110; + public static final int SDLK_o = 111; + public static final int SDLK_p = 112; + public static final int SDLK_q = 113; + public static final int SDLK_r = 114; + public static final int SDLK_s = 115; + public static final int SDLK_t = 116; + public static final int SDLK_u = 117; + public static final int SDLK_v = 118; + public static final int SDLK_w = 119; + public static final int SDLK_x = 120; + public static final int SDLK_y = 121; + public static final int SDLK_z = 122; + public static final int SDLK_DELETE = 127; + public static final int SDLK_WORLD_0 = 160; + public static final int SDLK_WORLD_1 = 161; + public static final int SDLK_WORLD_2 = 162; + public static final int SDLK_WORLD_3 = 163; + public static final int SDLK_WORLD_4 = 164; + public static final int SDLK_WORLD_5 = 165; + public static final int SDLK_WORLD_6 = 166; + public static final int SDLK_WORLD_7 = 167; + public static final int SDLK_WORLD_8 = 168; + public static final int SDLK_WORLD_9 = 169; + public static final int SDLK_WORLD_10 = 170; + public static final int SDLK_WORLD_11 = 171; + public static final int SDLK_WORLD_12 = 172; + public static final int SDLK_WORLD_13 = 173; + public static final int SDLK_WORLD_14 = 174; + public static final int SDLK_WORLD_15 = 175; + public static final int SDLK_WORLD_16 = 176; + public static final int SDLK_WORLD_17 = 177; + public static final int SDLK_WORLD_18 = 178; + public static final int SDLK_WORLD_19 = 179; + public static final int SDLK_WORLD_20 = 180; + public static final int SDLK_WORLD_21 = 181; + public static final int SDLK_WORLD_22 = 182; + public static final int SDLK_WORLD_23 = 183; + public static final int SDLK_WORLD_24 = 184; + public static final int SDLK_WORLD_25 = 185; + public static final int SDLK_WORLD_26 = 186; + public static final int SDLK_WORLD_27 = 187; + public static final int SDLK_WORLD_28 = 188; + public static final int SDLK_WORLD_29 = 189; + public static final int SDLK_WORLD_30 = 190; + public static final int SDLK_WORLD_31 = 191; + public static final int SDLK_WORLD_32 = 192; + public static final int SDLK_WORLD_33 = 193; + public static final int SDLK_WORLD_34 = 194; + public static final int SDLK_WORLD_35 = 195; + public static final int SDLK_WORLD_36 = 196; + public static final int SDLK_WORLD_37 = 197; + public static final int SDLK_WORLD_38 = 198; + public static final int SDLK_WORLD_39 = 199; + public static final int SDLK_WORLD_40 = 200; + public static final int SDLK_WORLD_41 = 201; + public static final int SDLK_WORLD_42 = 202; + public static final int SDLK_WORLD_43 = 203; + public static final int SDLK_WORLD_44 = 204; + public static final int SDLK_WORLD_45 = 205; + public static final int SDLK_WORLD_46 = 206; + public static final int SDLK_WORLD_47 = 207; + public static final int SDLK_WORLD_48 = 208; + public static final int SDLK_WORLD_49 = 209; + public static final int SDLK_WORLD_50 = 210; + public static final int SDLK_WORLD_51 = 211; + public static final int SDLK_WORLD_52 = 212; + public static final int SDLK_WORLD_53 = 213; + public static final int SDLK_WORLD_54 = 214; + public static final int SDLK_WORLD_55 = 215; + public static final int SDLK_WORLD_56 = 216; + public static final int SDLK_WORLD_57 = 217; + public static final int SDLK_WORLD_58 = 218; + public static final int SDLK_WORLD_59 = 219; + public static final int SDLK_WORLD_60 = 220; + public static final int SDLK_WORLD_61 = 221; + public static final int SDLK_WORLD_62 = 222; + public static final int SDLK_WORLD_63 = 223; + public static final int SDLK_WORLD_64 = 224; + public static final int SDLK_WORLD_65 = 225; + public static final int SDLK_WORLD_66 = 226; + public static final int SDLK_WORLD_67 = 227; + public static final int SDLK_WORLD_68 = 228; + public static final int SDLK_WORLD_69 = 229; + public static final int SDLK_WORLD_70 = 230; + public static final int SDLK_WORLD_71 = 231; + public static final int SDLK_WORLD_72 = 232; + public static final int SDLK_WORLD_73 = 233; + public static final int SDLK_WORLD_74 = 234; + public static final int SDLK_WORLD_75 = 235; + public static final int SDLK_WORLD_76 = 236; + public static final int SDLK_WORLD_77 = 237; + public static final int SDLK_WORLD_78 = 238; + public static final int SDLK_WORLD_79 = 239; + public static final int SDLK_WORLD_80 = 240; + public static final int SDLK_WORLD_81 = 241; + public static final int SDLK_WORLD_82 = 242; + public static final int SDLK_WORLD_83 = 243; + public static final int SDLK_WORLD_84 = 244; + public static final int SDLK_WORLD_85 = 245; + public static final int SDLK_WORLD_86 = 246; + public static final int SDLK_WORLD_87 = 247; + public static final int SDLK_WORLD_88 = 248; + public static final int SDLK_WORLD_89 = 249; + public static final int SDLK_WORLD_90 = 250; + public static final int SDLK_WORLD_91 = 251; + public static final int SDLK_WORLD_92 = 252; + public static final int SDLK_WORLD_93 = 253; + public static final int SDLK_WORLD_94 = 254; + public static final int SDLK_WORLD_95 = 255; + public static final int SDLK_KP0 = 256; + public static final int SDLK_KP1 = 257; + public static final int SDLK_KP2 = 258; + public static final int SDLK_KP3 = 259; + public static final int SDLK_KP4 = 260; + public static final int SDLK_KP5 = 261; + public static final int SDLK_KP6 = 262; + public static final int SDLK_KP7 = 263; + public static final int SDLK_KP8 = 264; + public static final int SDLK_KP9 = 265; + public static final int SDLK_KP_PERIOD = 266; + public static final int SDLK_KP_DIVIDE = 267; + public static final int SDLK_KP_MULTIPLY = 268; + public static final int SDLK_KP_MINUS = 269; + public static final int SDLK_KP_PLUS = 270; + public static final int SDLK_KP_ENTER = 271; + public static final int SDLK_KP_EQUALS = 272; + public static final int SDLK_UP = 273; + public static final int SDLK_DOWN = 274; + public static final int SDLK_RIGHT = 275; + public static final int SDLK_LEFT = 276; + public static final int SDLK_INSERT = 277; + public static final int SDLK_HOME = 278; + public static final int SDLK_END = 279; + public static final int SDLK_PAGEUP = 280; + public static final int SDLK_PAGEDOWN = 281; + public static final int SDLK_F1 = 282; + public static final int SDLK_F2 = 283; + public static final int SDLK_F3 = 284; + public static final int SDLK_F4 = 285; + public static final int SDLK_F5 = 286; + public static final int SDLK_F6 = 287; + public static final int SDLK_F7 = 288; + public static final int SDLK_F8 = 289; + public static final int SDLK_F9 = 290; + public static final int SDLK_F10 = 291; + public static final int SDLK_F11 = 292; + public static final int SDLK_F12 = 293; + public static final int SDLK_F13 = 294; + public static final int SDLK_F14 = 295; + public static final int SDLK_F15 = 296; + public static final int SDLK_NUMLOCK = 300; + public static final int SDLK_CAPSLOCK = 301; + public static final int SDLK_SCROLLOCK = 302; + public static final int SDLK_RSHIFT = 303; + public static final int SDLK_LSHIFT = 304; + public static final int SDLK_RCTRL = 305; + public static final int SDLK_LCTRL = 306; + public static final int SDLK_RALT = 307; + public static final int SDLK_LALT = 308; + public static final int SDLK_RMETA = 309; + public static final int SDLK_LMETA = 310; + public static final int SDLK_LSUPER = 311; + public static final int SDLK_RSUPER = 312; + public static final int SDLK_MODE = 313; + public static final int SDLK_COMPOSE = 314; + public static final int SDLK_HELP = 315; + public static final int SDLK_PRINT = 316; + public static final int SDLK_SYSREQ = 317; + public static final int SDLK_BREAK = 318; + public static final int SDLK_MENU = 319; + public static final int SDLK_POWER = 320; + public static final int SDLK_EURO = 321; + public static final int SDLK_UNDO = 322; - // Mouse buttons can be mapped to on-screen keys - public static final int SDLK_MOUSE_LEFT = 500; - public static final int SDLK_MOUSE_MIDDLE = 501; - public static final int SDLK_MOUSE_RIGHT = 502; - public static final int SDLK_MOUSE_WHEEL_UP = 503; - public static final int SDLK_MOUSE_WHEEL_DOWN = 504; - public static final int SDLK_MOUSE_X1 = 505; - public static final int SDLK_MOUSE_X2 = 506; + // Mouse buttons can be mapped to on-screen keys + public static final int SDLK_MOUSE_LEFT = 500; + public static final int SDLK_MOUSE_MIDDLE = 501; + public static final int SDLK_MOUSE_RIGHT = 502; + public static final int SDLK_MOUSE_WHEEL_UP = 503; + public static final int SDLK_MOUSE_WHEEL_DOWN = 504; + public static final int SDLK_MOUSE_X1 = 505; + public static final int SDLK_MOUSE_X2 = 506; - public static final int SDLK_NO_REMAP = 512; + public static final int SDLK_NO_REMAP = 512; } // Autogenerated by hand with a command: // grep 'SDL_SCANCODE_' SDL_scancode.h | sed 's/SDL_SCANCODE_\([a-zA-Z0-9_]\+\).*[=] \([0-9]\+\).*/public static final int SDLK_\1 = \2;/' >> Keycodes.java -class SDL_1_3_Keycodes -{ - public static final int SDLK_UNKNOWN = 0; - public static final int SDLK_A = 4; - public static final int SDLK_B = 5; - public static final int SDLK_C = 6; - public static final int SDLK_D = 7; - public static final int SDLK_E = 8; - public static final int SDLK_F = 9; - public static final int SDLK_G = 10; - public static final int SDLK_H = 11; - public static final int SDLK_I = 12; - public static final int SDLK_J = 13; - public static final int SDLK_K = 14; - public static final int SDLK_L = 15; - public static final int SDLK_M = 16; - public static final int SDLK_N = 17; - public static final int SDLK_O = 18; - public static final int SDLK_P = 19; - public static final int SDLK_Q = 20; - public static final int SDLK_R = 21; - public static final int SDLK_S = 22; - public static final int SDLK_T = 23; - public static final int SDLK_U = 24; - public static final int SDLK_V = 25; - public static final int SDLK_W = 26; - public static final int SDLK_X = 27; - public static final int SDLK_Y = 28; - public static final int SDLK_Z = 29; - public static final int SDLK_1 = 30; - public static final int SDLK_2 = 31; - public static final int SDLK_3 = 32; - public static final int SDLK_4 = 33; - public static final int SDLK_5 = 34; - public static final int SDLK_6 = 35; - public static final int SDLK_7 = 36; - public static final int SDLK_8 = 37; - public static final int SDLK_9 = 38; - public static final int SDLK_0 = 39; - public static final int SDLK_RETURN = 40; - public static final int SDLK_ESCAPE = 41; - public static final int SDLK_BACKSPACE = 42; - public static final int SDLK_TAB = 43; - public static final int SDLK_SPACE = 44; - public static final int SDLK_MINUS = 45; - public static final int SDLK_EQUALS = 46; - public static final int SDLK_LEFTBRACKET = 47; - public static final int SDLK_RIGHTBRACKET = 48; - public static final int SDLK_BACKSLASH = 49; - public static final int SDLK_NONUSHASH = 50; - public static final int SDLK_SEMICOLON = 51; - public static final int SDLK_APOSTROPHE = 52; - public static final int SDLK_GRAVE = 53; - public static final int SDLK_COMMA = 54; - public static final int SDLK_PERIOD = 55; - public static final int SDLK_SLASH = 56; - public static final int SDLK_CAPSLOCK = 57; - public static final int SDLK_F1 = 58; - public static final int SDLK_F2 = 59; - public static final int SDLK_F3 = 60; - public static final int SDLK_F4 = 61; - public static final int SDLK_F5 = 62; - public static final int SDLK_F6 = 63; - public static final int SDLK_F7 = 64; - public static final int SDLK_F8 = 65; - public static final int SDLK_F9 = 66; - public static final int SDLK_F10 = 67; - public static final int SDLK_F11 = 68; - public static final int SDLK_F12 = 69; - public static final int SDLK_PRINTSCREEN = 70; - public static final int SDLK_SCROLLLOCK = 71; - public static final int SDLK_PAUSE = 72; - public static final int SDLK_INSERT = 73; - public static final int SDLK_HOME = 74; - public static final int SDLK_PAGEUP = 75; - public static final int SDLK_DELETE = 76; - public static final int SDLK_END = 77; - public static final int SDLK_PAGEDOWN = 78; - public static final int SDLK_RIGHT = 79; - public static final int SDLK_LEFT = 80; - public static final int SDLK_DOWN = 81; - public static final int SDLK_UP = 82; - public static final int SDLK_NUMLOCKCLEAR = 83; - public static final int SDLK_KP_DIVIDE = 84; - public static final int SDLK_KP_MULTIPLY = 85; - public static final int SDLK_KP_MINUS = 86; - public static final int SDLK_KP_PLUS = 87; - public static final int SDLK_KP_ENTER = 88; - public static final int SDLK_KP_1 = 89; - public static final int SDLK_KP_2 = 90; - public static final int SDLK_KP_3 = 91; - public static final int SDLK_KP_4 = 92; - public static final int SDLK_KP_5 = 93; - public static final int SDLK_KP_6 = 94; - public static final int SDLK_KP_7 = 95; - public static final int SDLK_KP_8 = 96; - public static final int SDLK_KP_9 = 97; - public static final int SDLK_KP_0 = 98; - public static final int SDLK_KP_PERIOD = 99; - public static final int SDLK_NONUSBACKSLASH = 100; - public static final int SDLK_APPLICATION = 101; - public static final int SDLK_POWER = 102; - public static final int SDLK_KP_EQUALS = 103; - public static final int SDLK_F13 = 104; - public static final int SDLK_F14 = 105; - public static final int SDLK_F15 = 106; - public static final int SDLK_F16 = 107; - public static final int SDLK_F17 = 108; - public static final int SDLK_F18 = 109; - public static final int SDLK_F19 = 110; - public static final int SDLK_F20 = 111; - public static final int SDLK_F21 = 112; - public static final int SDLK_F22 = 113; - public static final int SDLK_F23 = 114; - public static final int SDLK_F24 = 115; - public static final int SDLK_EXECUTE = 116; - public static final int SDLK_HELP = 117; - public static final int SDLK_MENU = 118; - public static final int SDLK_SELECT = 119; - public static final int SDLK_STOP = 120; - public static final int SDLK_AGAIN = 121; - public static final int SDLK_UNDO = 122; - public static final int SDLK_CUT = 123; - public static final int SDLK_COPY = 124; - public static final int SDLK_PASTE = 125; - public static final int SDLK_FIND = 126; - public static final int SDLK_MUTE = 127; - public static final int SDLK_VOLUMEUP = 128; - public static final int SDLK_VOLUMEDOWN = 129; - public static final int SDLK_KP_COMMA = 133; - public static final int SDLK_KP_EQUALSAS400 = 134; - public static final int SDLK_INTERNATIONAL1 = 135; - public static final int SDLK_INTERNATIONAL2 = 136; - public static final int SDLK_INTERNATIONAL3 = 137; - public static final int SDLK_INTERNATIONAL4 = 138; - public static final int SDLK_INTERNATIONAL5 = 139; - public static final int SDLK_INTERNATIONAL6 = 140; - public static final int SDLK_INTERNATIONAL7 = 141; - public static final int SDLK_INTERNATIONAL8 = 142; - public static final int SDLK_INTERNATIONAL9 = 143; - public static final int SDLK_LANG1 = 144; - public static final int SDLK_LANG2 = 145; - public static final int SDLK_LANG3 = 146; - public static final int SDLK_LANG4 = 147; - public static final int SDLK_LANG5 = 148; - public static final int SDLK_LANG6 = 149; - public static final int SDLK_LANG7 = 150; - public static final int SDLK_LANG8 = 151; - public static final int SDLK_LANG9 = 152; - public static final int SDLK_ALTERASE = 153; - public static final int SDLK_SYSREQ = 154; - public static final int SDLK_CANCEL = 155; - public static final int SDLK_CLEAR = 156; - public static final int SDLK_PRIOR = 157; - public static final int SDLK_RETURN2 = 158; - public static final int SDLK_SEPARATOR = 159; - public static final int SDLK_OUT = 160; - public static final int SDLK_OPER = 161; - public static final int SDLK_CLEARAGAIN = 162; - public static final int SDLK_CRSEL = 163; - public static final int SDLK_EXSEL = 164; - public static final int SDLK_KP_00 = 176; - public static final int SDLK_KP_000 = 177; - public static final int SDLK_THOUSANDSSEPARATOR = 178; - public static final int SDLK_DECIMALSEPARATOR = 179; - public static final int SDLK_CURRENCYUNIT = 180; - public static final int SDLK_CURRENCYSUBUNIT = 181; - public static final int SDLK_KP_LEFTPAREN = 182; - public static final int SDLK_KP_RIGHTPAREN = 183; - public static final int SDLK_KP_LEFTBRACE = 184; - public static final int SDLK_KP_RIGHTBRACE = 185; - public static final int SDLK_KP_TAB = 186; - public static final int SDLK_KP_BACKSPACE = 187; - public static final int SDLK_KP_A = 188; - public static final int SDLK_KP_B = 189; - public static final int SDLK_KP_C = 190; - public static final int SDLK_KP_D = 191; - public static final int SDLK_KP_E = 192; - public static final int SDLK_KP_F = 193; - public static final int SDLK_KP_XOR = 194; - public static final int SDLK_KP_POWER = 195; - public static final int SDLK_KP_PERCENT = 196; - public static final int SDLK_KP_LESS = 197; - public static final int SDLK_KP_GREATER = 198; - public static final int SDLK_KP_AMPERSAND = 199; - public static final int SDLK_KP_DBLAMPERSAND = 200; - public static final int SDLK_KP_VERTICALBAR = 201; - public static final int SDLK_KP_DBLVERTICALBAR = 202; - public static final int SDLK_KP_COLON = 203; - public static final int SDLK_KP_HASH = 204; - public static final int SDLK_KP_SPACE = 205; - public static final int SDLK_KP_AT = 206; - public static final int SDLK_KP_EXCLAM = 207; - public static final int SDLK_KP_MEMSTORE = 208; - public static final int SDLK_KP_MEMRECALL = 209; - public static final int SDLK_KP_MEMCLEAR = 210; - public static final int SDLK_KP_MEMADD = 211; - public static final int SDLK_KP_MEMSUBTRACT = 212; - public static final int SDLK_KP_MEMMULTIPLY = 213; - public static final int SDLK_KP_MEMDIVIDE = 214; - public static final int SDLK_KP_PLUSMINUS = 215; - public static final int SDLK_KP_CLEAR = 216; - public static final int SDLK_KP_CLEARENTRY = 217; - public static final int SDLK_KP_BINARY = 218; - public static final int SDLK_KP_OCTAL = 219; - public static final int SDLK_KP_DECIMAL = 220; - public static final int SDLK_KP_HEXADECIMAL = 221; - public static final int SDLK_LCTRL = 224; - public static final int SDLK_LSHIFT = 225; - public static final int SDLK_LALT = 226; - public static final int SDLK_LGUI = 227; - public static final int SDLK_RCTRL = 228; - public static final int SDLK_RSHIFT = 229; - public static final int SDLK_RALT = 230; - public static final int SDLK_RGUI = 231; - public static final int SDLK_MODE = 257; - public static final int SDLK_AUDIONEXT = 258; - public static final int SDLK_AUDIOPREV = 259; - public static final int SDLK_AUDIOSTOP = 260; - public static final int SDLK_AUDIOPLAY = 261; - public static final int SDLK_AUDIOMUTE = 262; - public static final int SDLK_MEDIASELECT = 263; - public static final int SDLK_WWW = 264; - public static final int SDLK_MAIL = 265; - public static final int SDLK_CALCULATOR = 266; - public static final int SDLK_COMPUTER = 267; - public static final int SDLK_AC_SEARCH = 268; - public static final int SDLK_AC_HOME = 269; - public static final int SDLK_AC_BACK = 270; - public static final int SDLK_AC_FORWARD = 271; - public static final int SDLK_AC_STOP = 272; - public static final int SDLK_AC_REFRESH = 273; - public static final int SDLK_AC_BOOKMARKS = 274; - public static final int SDLK_BRIGHTNESSDOWN = 275; - public static final int SDLK_BRIGHTNESSUP = 276; - public static final int SDLK_DISPLAYSWITCH = 277; - public static final int SDLK_KBDILLUMTOGGLE = 278; - public static final int SDLK_KBDILLUMDOWN = 279; - public static final int SDLK_KBDILLUMUP = 280; - public static final int SDLK_EJECT = 281; - public static final int SDLK_SLEEP = 282; +class SDL_1_3_Keycodes { + public static final int SDLK_UNKNOWN = 0; + public static final int SDLK_A = 4; + public static final int SDLK_B = 5; + public static final int SDLK_C = 6; + public static final int SDLK_D = 7; + public static final int SDLK_E = 8; + public static final int SDLK_F = 9; + public static final int SDLK_G = 10; + public static final int SDLK_H = 11; + public static final int SDLK_I = 12; + public static final int SDLK_J = 13; + public static final int SDLK_K = 14; + public static final int SDLK_L = 15; + public static final int SDLK_M = 16; + public static final int SDLK_N = 17; + public static final int SDLK_O = 18; + public static final int SDLK_P = 19; + public static final int SDLK_Q = 20; + public static final int SDLK_R = 21; + public static final int SDLK_S = 22; + public static final int SDLK_T = 23; + public static final int SDLK_U = 24; + public static final int SDLK_V = 25; + public static final int SDLK_W = 26; + public static final int SDLK_X = 27; + public static final int SDLK_Y = 28; + public static final int SDLK_Z = 29; + public static final int SDLK_1 = 30; + public static final int SDLK_2 = 31; + public static final int SDLK_3 = 32; + public static final int SDLK_4 = 33; + public static final int SDLK_5 = 34; + public static final int SDLK_6 = 35; + public static final int SDLK_7 = 36; + public static final int SDLK_8 = 37; + public static final int SDLK_9 = 38; + public static final int SDLK_0 = 39; + public static final int SDLK_RETURN = 40; + public static final int SDLK_ESCAPE = 41; + public static final int SDLK_BACKSPACE = 42; + public static final int SDLK_TAB = 43; + public static final int SDLK_SPACE = 44; + public static final int SDLK_MINUS = 45; + public static final int SDLK_EQUALS = 46; + public static final int SDLK_LEFTBRACKET = 47; + public static final int SDLK_RIGHTBRACKET = 48; + public static final int SDLK_BACKSLASH = 49; + public static final int SDLK_NONUSHASH = 50; + public static final int SDLK_SEMICOLON = 51; + public static final int SDLK_APOSTROPHE = 52; + public static final int SDLK_GRAVE = 53; + public static final int SDLK_COMMA = 54; + public static final int SDLK_PERIOD = 55; + public static final int SDLK_SLASH = 56; + public static final int SDLK_CAPSLOCK = 57; + public static final int SDLK_F1 = 58; + public static final int SDLK_F2 = 59; + public static final int SDLK_F3 = 60; + public static final int SDLK_F4 = 61; + public static final int SDLK_F5 = 62; + public static final int SDLK_F6 = 63; + public static final int SDLK_F7 = 64; + public static final int SDLK_F8 = 65; + public static final int SDLK_F9 = 66; + public static final int SDLK_F10 = 67; + public static final int SDLK_F11 = 68; + public static final int SDLK_F12 = 69; + public static final int SDLK_PRINTSCREEN = 70; + public static final int SDLK_SCROLLLOCK = 71; + public static final int SDLK_PAUSE = 72; + public static final int SDLK_INSERT = 73; + public static final int SDLK_HOME = 74; + public static final int SDLK_PAGEUP = 75; + public static final int SDLK_DELETE = 76; + public static final int SDLK_END = 77; + public static final int SDLK_PAGEDOWN = 78; + public static final int SDLK_RIGHT = 79; + public static final int SDLK_LEFT = 80; + public static final int SDLK_DOWN = 81; + public static final int SDLK_UP = 82; + public static final int SDLK_NUMLOCKCLEAR = 83; + public static final int SDLK_KP_DIVIDE = 84; + public static final int SDLK_KP_MULTIPLY = 85; + public static final int SDLK_KP_MINUS = 86; + public static final int SDLK_KP_PLUS = 87; + public static final int SDLK_KP_ENTER = 88; + public static final int SDLK_KP_1 = 89; + public static final int SDLK_KP_2 = 90; + public static final int SDLK_KP_3 = 91; + public static final int SDLK_KP_4 = 92; + public static final int SDLK_KP_5 = 93; + public static final int SDLK_KP_6 = 94; + public static final int SDLK_KP_7 = 95; + public static final int SDLK_KP_8 = 96; + public static final int SDLK_KP_9 = 97; + public static final int SDLK_KP_0 = 98; + public static final int SDLK_KP_PERIOD = 99; + public static final int SDLK_NONUSBACKSLASH = 100; + public static final int SDLK_APPLICATION = 101; + public static final int SDLK_POWER = 102; + public static final int SDLK_KP_EQUALS = 103; + public static final int SDLK_F13 = 104; + public static final int SDLK_F14 = 105; + public static final int SDLK_F15 = 106; + public static final int SDLK_F16 = 107; + public static final int SDLK_F17 = 108; + public static final int SDLK_F18 = 109; + public static final int SDLK_F19 = 110; + public static final int SDLK_F20 = 111; + public static final int SDLK_F21 = 112; + public static final int SDLK_F22 = 113; + public static final int SDLK_F23 = 114; + public static final int SDLK_F24 = 115; + public static final int SDLK_EXECUTE = 116; + public static final int SDLK_HELP = 117; + public static final int SDLK_MENU = 118; + public static final int SDLK_SELECT = 119; + public static final int SDLK_STOP = 120; + public static final int SDLK_AGAIN = 121; + public static final int SDLK_UNDO = 122; + public static final int SDLK_CUT = 123; + public static final int SDLK_COPY = 124; + public static final int SDLK_PASTE = 125; + public static final int SDLK_FIND = 126; + public static final int SDLK_MUTE = 127; + public static final int SDLK_VOLUMEUP = 128; + public static final int SDLK_VOLUMEDOWN = 129; + public static final int SDLK_KP_COMMA = 133; + public static final int SDLK_KP_EQUALSAS400 = 134; + public static final int SDLK_INTERNATIONAL1 = 135; + public static final int SDLK_INTERNATIONAL2 = 136; + public static final int SDLK_INTERNATIONAL3 = 137; + public static final int SDLK_INTERNATIONAL4 = 138; + public static final int SDLK_INTERNATIONAL5 = 139; + public static final int SDLK_INTERNATIONAL6 = 140; + public static final int SDLK_INTERNATIONAL7 = 141; + public static final int SDLK_INTERNATIONAL8 = 142; + public static final int SDLK_INTERNATIONAL9 = 143; + public static final int SDLK_LANG1 = 144; + public static final int SDLK_LANG2 = 145; + public static final int SDLK_LANG3 = 146; + public static final int SDLK_LANG4 = 147; + public static final int SDLK_LANG5 = 148; + public static final int SDLK_LANG6 = 149; + public static final int SDLK_LANG7 = 150; + public static final int SDLK_LANG8 = 151; + public static final int SDLK_LANG9 = 152; + public static final int SDLK_ALTERASE = 153; + public static final int SDLK_SYSREQ = 154; + public static final int SDLK_CANCEL = 155; + public static final int SDLK_CLEAR = 156; + public static final int SDLK_PRIOR = 157; + public static final int SDLK_RETURN2 = 158; + public static final int SDLK_SEPARATOR = 159; + public static final int SDLK_OUT = 160; + public static final int SDLK_OPER = 161; + public static final int SDLK_CLEARAGAIN = 162; + public static final int SDLK_CRSEL = 163; + public static final int SDLK_EXSEL = 164; + public static final int SDLK_KP_00 = 176; + public static final int SDLK_KP_000 = 177; + public static final int SDLK_THOUSANDSSEPARATOR = 178; + public static final int SDLK_DECIMALSEPARATOR = 179; + public static final int SDLK_CURRENCYUNIT = 180; + public static final int SDLK_CURRENCYSUBUNIT = 181; + public static final int SDLK_KP_LEFTPAREN = 182; + public static final int SDLK_KP_RIGHTPAREN = 183; + public static final int SDLK_KP_LEFTBRACE = 184; + public static final int SDLK_KP_RIGHTBRACE = 185; + public static final int SDLK_KP_TAB = 186; + public static final int SDLK_KP_BACKSPACE = 187; + public static final int SDLK_KP_A = 188; + public static final int SDLK_KP_B = 189; + public static final int SDLK_KP_C = 190; + public static final int SDLK_KP_D = 191; + public static final int SDLK_KP_E = 192; + public static final int SDLK_KP_F = 193; + public static final int SDLK_KP_XOR = 194; + public static final int SDLK_KP_POWER = 195; + public static final int SDLK_KP_PERCENT = 196; + public static final int SDLK_KP_LESS = 197; + public static final int SDLK_KP_GREATER = 198; + public static final int SDLK_KP_AMPERSAND = 199; + public static final int SDLK_KP_DBLAMPERSAND = 200; + public static final int SDLK_KP_VERTICALBAR = 201; + public static final int SDLK_KP_DBLVERTICALBAR = 202; + public static final int SDLK_KP_COLON = 203; + public static final int SDLK_KP_HASH = 204; + public static final int SDLK_KP_SPACE = 205; + public static final int SDLK_KP_AT = 206; + public static final int SDLK_KP_EXCLAM = 207; + public static final int SDLK_KP_MEMSTORE = 208; + public static final int SDLK_KP_MEMRECALL = 209; + public static final int SDLK_KP_MEMCLEAR = 210; + public static final int SDLK_KP_MEMADD = 211; + public static final int SDLK_KP_MEMSUBTRACT = 212; + public static final int SDLK_KP_MEMMULTIPLY = 213; + public static final int SDLK_KP_MEMDIVIDE = 214; + public static final int SDLK_KP_PLUSMINUS = 215; + public static final int SDLK_KP_CLEAR = 216; + public static final int SDLK_KP_CLEARENTRY = 217; + public static final int SDLK_KP_BINARY = 218; + public static final int SDLK_KP_OCTAL = 219; + public static final int SDLK_KP_DECIMAL = 220; + public static final int SDLK_KP_HEXADECIMAL = 221; + public static final int SDLK_LCTRL = 224; + public static final int SDLK_LSHIFT = 225; + public static final int SDLK_LALT = 226; + public static final int SDLK_LGUI = 227; + public static final int SDLK_RCTRL = 228; + public static final int SDLK_RSHIFT = 229; + public static final int SDLK_RALT = 230; + public static final int SDLK_RGUI = 231; + public static final int SDLK_MODE = 257; + public static final int SDLK_AUDIONEXT = 258; + public static final int SDLK_AUDIOPREV = 259; + public static final int SDLK_AUDIOSTOP = 260; + public static final int SDLK_AUDIOPLAY = 261; + public static final int SDLK_AUDIOMUTE = 262; + public static final int SDLK_MEDIASELECT = 263; + public static final int SDLK_WWW = 264; + public static final int SDLK_MAIL = 265; + public static final int SDLK_CALCULATOR = 266; + public static final int SDLK_COMPUTER = 267; + public static final int SDLK_AC_SEARCH = 268; + public static final int SDLK_AC_HOME = 269; + public static final int SDLK_AC_BACK = 270; + public static final int SDLK_AC_FORWARD = 271; + public static final int SDLK_AC_STOP = 272; + public static final int SDLK_AC_REFRESH = 273; + public static final int SDLK_AC_BOOKMARKS = 274; + public static final int SDLK_BRIGHTNESSDOWN = 275; + public static final int SDLK_BRIGHTNESSUP = 276; + public static final int SDLK_DISPLAYSWITCH = 277; + public static final int SDLK_KBDILLUMTOGGLE = 278; + public static final int SDLK_KBDILLUMDOWN = 279; + public static final int SDLK_KBDILLUMUP = 280; + public static final int SDLK_EJECT = 281; + public static final int SDLK_SLEEP = 282; - // Mouse buttons can be mapped to on-screen keys - public static final int SDLK_MOUSE_LEFT = 500; - public static final int SDLK_MOUSE_MIDDLE = 501; - public static final int SDLK_MOUSE_RIGHT = 502; - public static final int SDLK_MOUSE_WHEEL_UP = 503; - public static final int SDLK_MOUSE_WHEEL_DOWN = 504; - public static final int SDLK_MOUSE_X1 = 505; - public static final int SDLK_MOUSE_X2 = 506; + // Mouse buttons can be mapped to on-screen keys + public static final int SDLK_MOUSE_LEFT = 500; + public static final int SDLK_MOUSE_MIDDLE = 501; + public static final int SDLK_MOUSE_RIGHT = 502; + public static final int SDLK_MOUSE_WHEEL_UP = 503; + public static final int SDLK_MOUSE_WHEEL_DOWN = 504; + public static final int SDLK_MOUSE_X1 = 505; + public static final int SDLK_MOUSE_X2 = 506; - public static final int SDLK_NO_REMAP = 512; + public static final int SDLK_NO_REMAP = 512; } -class SDL_Keys -{ - public static String [] names = null; - public static Integer [] values = null; +class SDL_Keys { + public static String[] names = null; + public static Integer[] values = null; - public static String [] namesSorted = null; - public static Integer [] namesSortedIdx = null; - public static Integer [] namesSortedBackIdx = null; - - static final int JAVA_KEYCODE_LAST = 255; // Android 2.3 added several new gaming keys, Android 3.1 added even more - keep in sync with javakeycodes.h + public static String[] namesSorted = null; + public static Integer[] namesSortedIdx = null; + public static Integer[] namesSortedBackIdx = null; - static String getName(int v) - { - for( int f = 0; f < values.length; f++ ) - { - if( values[f] == v ) - return names[f]; - } - return names[0]; - } + static final int JAVA_KEYCODE_LAST = 255; // Android 2.3 added several new gaming keys, Android 3.1 added even more - keep in sync with javakeycodes.h - static - { - ArrayList Names = new ArrayList (); - ArrayList Values = new ArrayList (); - Field [] fields = SDL_1_2_Keycodes.class.getDeclaredFields(); - if( Globals.Using_SDL_1_3 ) - { - fields = SDL_1_3_Keycodes.class.getDeclaredFields(); - } - - try { - for(Field f: fields) - { - if( !f.getName().startsWith("SDLK_") ) - { - continue; - } - Values.add(f.getInt(null)); - Names.add(f.getName().substring(5).toUpperCase()); - } - } catch(IllegalAccessException e) {}; - - // Sort by value - for( int i = 0; i < Values.size(); i++ ) - { - for( int j = i; j < Values.size(); j++ ) - { - if( Values.get(i) > Values.get(j) ) - { - int x = Values.get(i); - Values.set(i, Values.get(j)); - Values.set(j, x); - String s = Names.get(i); - Names.set(i, Names.get(j)); - Names.set(j, s); - } - } - } - - names = Names.toArray(new String[0]); - values = Values.toArray(new Integer[0]); - namesSorted = Names.toArray(new String[0]); - namesSortedIdx = new Integer[values.length]; - namesSortedBackIdx = new Integer[values.length]; - Arrays.sort(namesSorted); - for( int i = 0; i < namesSorted.length; i++ ) - { - for( int j = 0; j < namesSorted.length; j++ ) - { - if( namesSorted[i].equals( names[j] ) ) - { - namesSortedIdx[i] = j; - namesSortedBackIdx[j] = i; - break; - } - } - } - } + static String getName(int v) { + for (int f = 0; f < values.length; f++) { + if (values[f] == v) + return names[f]; + } + return names[0]; + } + + static { + ArrayList Names = new ArrayList(); + ArrayList Values = new ArrayList(); + Field[] fields = SDL_1_2_Keycodes.class.getDeclaredFields(); + if (Globals.Using_SDL_1_3) { + fields = SDL_1_3_Keycodes.class.getDeclaredFields(); + } + + try { + for (Field f : fields) { + if (!f.getName().startsWith("SDLK_")) { + continue; + } + Values.add(f.getInt(null)); + Names.add(f.getName().substring(5).toUpperCase()); + } + } catch (IllegalAccessException e) { + } + ; + + // Sort by value + for (int i = 0; i < Values.size(); i++) { + for (int j = i; j < Values.size(); j++) { + if (Values.get(i) > Values.get(j)) { + int x = Values.get(i); + Values.set(i, Values.get(j)); + Values.set(j, x); + String s = Names.get(i); + Names.set(i, Names.get(j)); + Names.set(j, s); + } + } + } + + names = Names.toArray(new String[0]); + values = Values.toArray(new Integer[0]); + namesSorted = Names.toArray(new String[0]); + namesSortedIdx = new Integer[values.length]; + namesSortedBackIdx = new Integer[values.length]; + Arrays.sort(namesSorted); + for (int i = 0; i < namesSorted.length; i++) { + for (int j = 0; j < namesSorted.length; j++) { + if (namesSorted[i].equals(names[j])) { + namesSortedIdx[i] = j; + namesSortedBackIdx[j] = i; + break; + } + } + } + } } diff --git a/Xorg/src/main/java/io/neoterm/MainActivity.java b/Xorg/src/main/java/io/neoterm/MainActivity.java index f9e461f..5a8eb71 100644 --- a/Xorg/src/main/java/io/neoterm/MainActivity.java +++ b/Xorg/src/main/java/io/neoterm/MainActivity.java @@ -39,259 +39,243 @@ import android.inputmethodservice.Keyboard; import android.inputmethodservice.KeyboardView; import android.os.Bundle; import android.os.SystemClock; -import androidx.appcompat.app.AppCompatActivity; import android.text.InputType; import android.text.SpannedString; import android.util.DisplayMetrics; import android.util.Log; -import android.view.Display; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.View; +import android.view.*; import android.view.View.OnKeyListener; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.Window; -import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; -import android.widget.Button; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; -import android.widget.TextView; +import android.widget.*; +import androidx.appcompat.app.AppCompatActivity; +import io.neoterm.xorg.NeoXorgViewClient; +import io.neoterm.xorg.R; import java.util.LinkedList; import java.util.TreeSet; import java.util.concurrent.Semaphore; -import io.neoterm.xorg.NeoXorgViewClient; -import io.neoterm.xorg.R; - public class MainActivity extends AppCompatActivity implements NeoXorgViewClient { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - instance = this; - // fullscreen mode - requestWindowFeature(Window.FEATURE_NO_TITLE); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - if (Globals.InhibitSuspend) - getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, - WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + instance = this; + // fullscreen mode + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + if (Globals.InhibitSuspend) + getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - Log.i("SDL", "libSDL: Creating startup screen"); - _layout = new LinearLayout(this); - _layout.setOrientation(LinearLayout.VERTICAL); - _layout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - _layout2 = new LinearLayout(this); - _layout2.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - loadingDialog = new ProgressDialog(this); - loadingDialog.setMessage(getString(R.string.accessing_network)); + Log.i("SDL", "libSDL: Creating startup screen"); + _layout = new LinearLayout(this); + _layout.setOrientation(LinearLayout.VERTICAL); + _layout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + _layout2 = new LinearLayout(this); + _layout2.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + loadingDialog = new ProgressDialog(this); + loadingDialog.setMessage(getString(R.string.accessing_network)); - final Semaphore loadedLibraries = new Semaphore(0); + final Semaphore loadedLibraries = new Semaphore(0); - if (Globals.StartupMenuButtonTimeout > 0) { - _btn = new Button(this); - _btn.setEnabled(false); - _btn.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - _btn.setText(getResources().getString(R.string.device_change_cfg)); - class onClickListener implements View.OnClickListener { - public MainActivity p; + if (Globals.StartupMenuButtonTimeout > 0) { + _btn = new Button(this); + _btn.setEnabled(false); + _btn.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + _btn.setText(getResources().getString(R.string.device_change_cfg)); + class onClickListener implements View.OnClickListener { + public MainActivity p; - onClickListener(MainActivity _p) { - p = _p; - } - - public void onClick(View v) { - setUpStatusLabel(); - Log.i("SDL", "libSDL: User clicked change phone config button"); - loadedLibraries.acquireUninterruptibly(); - setScreenOrientation(); - SettingsMenu.showConfig(p, false); - } - } - ; - _btn.setOnClickListener(new onClickListener(this)); - - _layout2.addView(_btn); + onClickListener(MainActivity _p) { + p = _p; } - _layout.addView(_layout2); + public void onClick(View v) { + setUpStatusLabel(); + Log.i("SDL", "libSDL: User clicked change phone config button"); + loadedLibraries.acquireUninterruptibly(); + setScreenOrientation(); + SettingsMenu.showConfig(p, false); + } + } + ; + _btn.setOnClickListener(new onClickListener(this)); - ImageView img = new ImageView(this); + _layout2.addView(_btn); + } - img.setScaleType(ImageView.ScaleType.FIT_CENTER /* FIT_XY */); + _layout.addView(_layout2); + + ImageView img = new ImageView(this); + + img.setScaleType(ImageView.ScaleType.FIT_CENTER /* FIT_XY */); + try { + img.setImageDrawable(Drawable.createFromStream(getAssets().open("logo.png"), "logo.png")); + } catch (Exception e) { + img.setImageResource(R.drawable.publisherlogo); + } + img.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + _layout.addView(img); + + _videoLayout = new FrameLayout(this); + _videoLayout.addView(_layout); + + setContentView(_videoLayout); + _videoLayout.setFocusable(true); + _videoLayout.setFocusableInTouchMode(true); + _videoLayout.requestFocus(); + + class Callback implements Runnable { + MainActivity p; + + Callback(MainActivity _p) { + p = _p; + } + + public void run() { try { - img.setImageDrawable(Drawable.createFromStream(getAssets().open("logo.png"), "logo.png")); - } catch (Exception e) { - img.setImageResource(R.drawable.publisherlogo); - } - img.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - _layout.addView(img); - - _videoLayout = new FrameLayout(this); - _videoLayout.addView(_layout); - - setContentView(_videoLayout); - _videoLayout.setFocusable(true); - _videoLayout.setFocusableInTouchMode(true); - _videoLayout.requestFocus(); - - class Callback implements Runnable { - MainActivity p; - - Callback(MainActivity _p) { - p = _p; - } - - public void run() { - try { - Thread.sleep(200); - } catch (InterruptedException e) { - } - ; - - if (p.mAudioThread == null) { - Log.i("SDL", "libSDL: Loading libraries"); - p.LoadLibraries(); - p.mAudioThread = new AudioThread(p); - Log.i("SDL", "libSDL: Loading settings"); - final Semaphore loaded = new Semaphore(0); - class Callback2 implements Runnable { - public MainActivity Parent; - - public void run() { - Settings.Load(Parent); - setScreenOrientation(); - loaded.release(); - loadedLibraries.release(); - if (_btn != null) { - _btn.setEnabled(true); - _btn.setFocusable(true); - _btn.setFocusableInTouchMode(true); - _btn.requestFocus(); - } - } - } - Callback2 cb = new Callback2(); - cb.Parent = p; - p.runOnUiThread(cb); - loaded.acquireUninterruptibly(); - if (!Globals.CompatibilityHacksStaticInit) - p.LoadApplicationLibrary(p); - } - - if (!Settings.settingsChanged) { - if (Globals.StartupMenuButtonTimeout > 0) { - Log.i("SDL", "libSDL: " + String.valueOf(Globals.StartupMenuButtonTimeout) + "-msec timeout in startup screen"); - try { - Thread.sleep(Globals.StartupMenuButtonTimeout); - } catch (InterruptedException e) { - } - ; - } - if (Settings.settingsChanged) - return; - } - } + Thread.sleep(200); + } catch (InterruptedException e) { } ; - (new Thread(new Callback(this))).start(); - // Request SD card permission right during start, because game devs don't care about runtime permissions and stuff - } - public void setUpStatusLabel() { - MainActivity Parent = this; // Too lazy to rename - if (Parent._btn != null) { - Parent._layout2.removeView(Parent._btn); - Parent._btn = null; - } - if (Parent._tv == null) { - //Get the display so we can know the screen size - Display display = getWindowManager().getDefaultDisplay(); - int width = display.getWidth(); - int height = display.getHeight(); - Parent._tv = new TextView(Parent); - Parent._tv.setMaxLines(2); // To show some long texts on smaller devices - Parent._tv.setMinLines(2); // Otherwise the background picture is getting resized at random, which does not look good - Parent._tv.setText(R.string.init); - // Padding is a good idea because if the display device is a TV the edges might be cut off - Parent._tv.setPadding((int) (width * 0.1), (int) (height * 0.1), (int) (width * 0.1), 0); - Parent._layout2.addView(Parent._tv); - } - } + if (p.mAudioThread == null) { + Log.i("SDL", "libSDL: Loading libraries"); + p.LoadLibraries(); + p.mAudioThread = new AudioThread(p); + Log.i("SDL", "libSDL: Loading settings"); + final Semaphore loaded = new Semaphore(0); + class Callback2 implements Runnable { + public MainActivity Parent; - public void initSDL() { - setScreenOrientation(); - updateScreenOrientation(); - DimSystemStatusBar.get().dim(_videoLayout); - (new Thread(new Runnable() { public void run() { - if (Globals.AutoDetectOrientation) - Globals.HorizontalOrientation = isCurrentOrientationHorizontal(); - while (isCurrentOrientationHorizontal() != Globals.HorizontalOrientation || - ((KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE)).inKeyguardRestrictedInputMode()) { - Log.d("SDL", "libSDL: Waiting for screen orientation to change to " + (Globals.HorizontalOrientation ? "landscape" : "portrait") + ", and for disabling lockscreen mode"); - try { - Thread.sleep(500); - } catch (Exception e) { - } - if (_isPaused) { - Log.i("SDL", "libSDL: Application paused, cancelling SDL initialization until it will be brought to foreground"); - return; - } - DimSystemStatusBar.get().dim(_videoLayout); - } - runOnUiThread(new Runnable() { - public void run() { - // Hide navigation buttons, and sleep a bit so OS will process the event. - // Do not check the display size in a loop - we may have several displays of different sizes, - // so app may stuck in infinite loop - DisplayMetrics dm = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(dm); - if (Globals.ImmersiveMode && (_videoLayout.getHeight() != dm.widthPixels || _videoLayout.getWidth() != dm.heightPixels)) { - DimSystemStatusBar.get().dim(_videoLayout); - try { - Thread.sleep(300); - } catch (Exception e) { - } - } - initSDLInternal(); - } - }); + Settings.Load(Parent); + setScreenOrientation(); + loaded.release(); + loadedLibraries.release(); + if (_btn != null) { + _btn.setEnabled(true); + _btn.setFocusable(true); + _btn.setFocusableInTouchMode(true); + _btn.requestFocus(); + } } - })).start(); - } + } + Callback2 cb = new Callback2(); + cb.Parent = p; + p.runOnUiThread(cb); + loaded.acquireUninterruptibly(); + if (!Globals.CompatibilityHacksStaticInit) + p.LoadApplicationLibrary(p); + } - private void initSDLInternal() { - if (sdlInited) + if (!Settings.settingsChanged) { + if (Globals.StartupMenuButtonTimeout > 0) { + Log.i("SDL", "libSDL: " + String.valueOf(Globals.StartupMenuButtonTimeout) + "-msec timeout in startup screen"); + try { + Thread.sleep(Globals.StartupMenuButtonTimeout); + } catch (InterruptedException e) { + } + ; + } + if (Settings.settingsChanged) return; - Log.i("SDL", "libSDL: Initializing video and SDL application"); + } + } + } + ; + (new Thread(new Callback(this))).start(); + // Request SD card permission right during start, because game devs don't care about runtime permissions and stuff + } - sdlInited = true; - DimSystemStatusBar.get().dim(_videoLayout); - _videoLayout.removeView(_layout); - _layout = null; - _layout2 = null; - _btn = null; - _tv = null; - _inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - _videoLayout = new FrameLayout(this); - SetLayerType.get().setLayerType(_videoLayout); - setContentView(_videoLayout); - mGLView = new NeoGLView(this); - SetLayerType.get().setLayerType(mGLView); - // Add TV screen borders, if needed - if (isRunningOnOUYA() && Globals.TvBorders) { - RelativeLayout view = new RelativeLayout(this); - RelativeLayout.LayoutParams layout; + public void setUpStatusLabel() { + MainActivity Parent = this; // Too lazy to rename + if (Parent._btn != null) { + Parent._layout2.removeView(Parent._btn); + Parent._btn = null; + } + if (Parent._tv == null) { + //Get the display so we can know the screen size + Display display = getWindowManager().getDefaultDisplay(); + int width = display.getWidth(); + int height = display.getHeight(); + Parent._tv = new TextView(Parent); + Parent._tv.setMaxLines(2); // To show some long texts on smaller devices + Parent._tv.setMinLines(2); // Otherwise the background picture is getting resized at random, which does not look good + Parent._tv.setText(R.string.init); + // Padding is a good idea because if the display device is a TV the edges might be cut off + Parent._tv.setPadding((int) (width * 0.1), (int) (height * 0.1), (int) (width * 0.1), 0); + Parent._layout2.addView(Parent._tv); + } + } + + public void initSDL() { + setScreenOrientation(); + updateScreenOrientation(); + DimSystemStatusBar.get().dim(_videoLayout); + (new Thread(new Runnable() { + public void run() { + if (Globals.AutoDetectOrientation) + Globals.HorizontalOrientation = isCurrentOrientationHorizontal(); + while (isCurrentOrientationHorizontal() != Globals.HorizontalOrientation || + ((KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE)).inKeyguardRestrictedInputMode()) { + Log.d("SDL", "libSDL: Waiting for screen orientation to change to " + (Globals.HorizontalOrientation ? "landscape" : "portrait") + ", and for disabling lockscreen mode"); + try { + Thread.sleep(500); + } catch (Exception e) { + } + if (_isPaused) { + Log.i("SDL", "libSDL: Application paused, cancelling SDL initialization until it will be brought to foreground"); + return; + } + DimSystemStatusBar.get().dim(_videoLayout); + } + runOnUiThread(new Runnable() { + public void run() { + // Hide navigation buttons, and sleep a bit so OS will process the event. + // Do not check the display size in a loop - we may have several displays of different sizes, + // so app may stuck in infinite loop + DisplayMetrics dm = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(dm); + if (Globals.ImmersiveMode && (_videoLayout.getHeight() != dm.widthPixels || _videoLayout.getWidth() != dm.heightPixels)) { + DimSystemStatusBar.get().dim(_videoLayout); + try { + Thread.sleep(300); + } catch (Exception e) { + } + } + initSDLInternal(); + } + }); + } + })).start(); + } + + private void initSDLInternal() { + if (sdlInited) + return; + Log.i("SDL", "libSDL: Initializing video and SDL application"); + + sdlInited = true; + DimSystemStatusBar.get().dim(_videoLayout); + _videoLayout.removeView(_layout); + _layout = null; + _layout2 = null; + _btn = null; + _tv = null; + _inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + _videoLayout = new FrameLayout(this); + SetLayerType.get().setLayerType(_videoLayout); + setContentView(_videoLayout); + mGLView = new NeoGLView(this); + SetLayerType.get().setLayerType(mGLView); + // Add TV screen borders, if needed + if (isRunningOnOUYA() && Globals.TvBorders) { + RelativeLayout view = new RelativeLayout(this); + RelativeLayout.LayoutParams layout; /* layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); @@ -301,387 +285,387 @@ public class MainActivity extends AppCompatActivity implements NeoXorgViewClient view.addView(mGLView); */ - layout = new RelativeLayout.LayoutParams(getResources().getDimensionPixelOffset(R.dimen.screen_border_horizontal), RelativeLayout.LayoutParams.MATCH_PARENT); - layout.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE); - layout.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); - ImageView borderLeft = new ImageView(this); - borderLeft.setId(R.id.left); // Any random ID - borderLeft.setImageResource(R.drawable.tv_border_left); - borderLeft.setScaleType(ImageView.ScaleType.FIT_XY); - view.addView(borderLeft, layout); + layout = new RelativeLayout.LayoutParams(getResources().getDimensionPixelOffset(R.dimen.screen_border_horizontal), RelativeLayout.LayoutParams.MATCH_PARENT); + layout.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE); + layout.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); + ImageView borderLeft = new ImageView(this); + borderLeft.setId(R.id.left); // Any random ID + borderLeft.setImageResource(R.drawable.tv_border_left); + borderLeft.setScaleType(ImageView.ScaleType.FIT_XY); + view.addView(borderLeft, layout); - layout = new RelativeLayout.LayoutParams(getResources().getDimensionPixelOffset(R.dimen.screen_border_horizontal), RelativeLayout.LayoutParams.MATCH_PARENT); - layout.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE); - layout.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); - ImageView borderRight = new ImageView(this); - borderRight.setId(R.id.right); - borderRight.setImageResource(R.drawable.tv_border_left); - borderRight.setScaleType(ImageView.ScaleType.FIT_XY); - borderRight.setScaleX(-1f); - view.addView(borderRight, layout); + layout = new RelativeLayout.LayoutParams(getResources().getDimensionPixelOffset(R.dimen.screen_border_horizontal), RelativeLayout.LayoutParams.MATCH_PARENT); + layout.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE); + layout.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); + ImageView borderRight = new ImageView(this); + borderRight.setId(R.id.right); + borderRight.setImageResource(R.drawable.tv_border_left); + borderRight.setScaleType(ImageView.ScaleType.FIT_XY); + borderRight.setScaleX(-1f); + view.addView(borderRight, layout); - layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, getResources().getDimensionPixelOffset(R.dimen.screen_border_vertical)); - layout.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); - layout.addRule(RelativeLayout.RIGHT_OF, borderLeft.getId()); - layout.addRule(RelativeLayout.LEFT_OF, borderRight.getId()); - ImageView borderTop = new ImageView(this); - borderTop.setId(R.id.top); - borderTop.setImageResource(R.drawable.tv_border_top); - borderTop.setScaleType(ImageView.ScaleType.FIT_XY); - view.addView(borderTop, layout); + layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, getResources().getDimensionPixelOffset(R.dimen.screen_border_vertical)); + layout.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); + layout.addRule(RelativeLayout.RIGHT_OF, borderLeft.getId()); + layout.addRule(RelativeLayout.LEFT_OF, borderRight.getId()); + ImageView borderTop = new ImageView(this); + borderTop.setId(R.id.top); + borderTop.setImageResource(R.drawable.tv_border_top); + borderTop.setScaleType(ImageView.ScaleType.FIT_XY); + view.addView(borderTop, layout); - layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, getResources().getDimensionPixelOffset(R.dimen.screen_border_vertical)); - layout.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); - layout.addRule(RelativeLayout.RIGHT_OF, borderLeft.getId()); - layout.addRule(RelativeLayout.LEFT_OF, borderRight.getId()); - ImageView borderBottom = new ImageView(this); - borderBottom.setId(R.id.bottom); - borderBottom.setImageResource(R.drawable.tv_border_top); - borderBottom.setScaleType(ImageView.ScaleType.FIT_XY); - borderBottom.setScaleY(-1f); - view.addView(borderBottom, layout); + layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, getResources().getDimensionPixelOffset(R.dimen.screen_border_vertical)); + layout.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); + layout.addRule(RelativeLayout.RIGHT_OF, borderLeft.getId()); + layout.addRule(RelativeLayout.LEFT_OF, borderRight.getId()); + ImageView borderBottom = new ImageView(this); + borderBottom.setId(R.id.bottom); + borderBottom.setImageResource(R.drawable.tv_border_top); + borderBottom.setScaleType(ImageView.ScaleType.FIT_XY); + borderBottom.setScaleY(-1f); + view.addView(borderBottom, layout); - layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); - layout.addRule(RelativeLayout.RIGHT_OF, borderLeft.getId()); - layout.addRule(RelativeLayout.LEFT_OF, borderRight.getId()); - layout.addRule(RelativeLayout.BELOW, borderTop.getId()); - layout.addRule(RelativeLayout.ABOVE, borderBottom.getId()); - mGLView.setLayoutParams(layout); + layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + layout.addRule(RelativeLayout.RIGHT_OF, borderLeft.getId()); + layout.addRule(RelativeLayout.LEFT_OF, borderRight.getId()); + layout.addRule(RelativeLayout.BELOW, borderTop.getId()); + layout.addRule(RelativeLayout.ABOVE, borderBottom.getId()); + mGLView.setLayoutParams(layout); - view.addView(mGLView); + view.addView(mGLView); - _videoLayout.addView(view, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); - } else { - _videoLayout.addView(mGLView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); - } - mGLView.setFocusableInTouchMode(true); - mGLView.setFocusable(true); - mGLView.requestFocus(); - if (Globals.HideSystemMousePointer && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - mGLView.setPointerIcon(android.view.PointerIcon.getSystemIcon(this, android.view.PointerIcon.TYPE_NULL)); - } + _videoLayout.addView(view, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); + } else { + _videoLayout.addView(mGLView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); + } + mGLView.setFocusableInTouchMode(true); + mGLView.setFocusable(true); + mGLView.requestFocus(); + if (Globals.HideSystemMousePointer && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + mGLView.setPointerIcon(android.view.PointerIcon.getSystemIcon(this, android.view.PointerIcon.TYPE_NULL)); + } - DimSystemStatusBar.get().dim(_videoLayout); - //DimSystemStatusBar.get().dim(mGLView); + DimSystemStatusBar.get().dim(_videoLayout); + //DimSystemStatusBar.get().dim(mGLView); - Rect r = new Rect(); + Rect r = new Rect(); + _videoLayout.getWindowVisibleDisplayFrame(r); + mGLView.nativeScreenVisibleRect(r.left, r.top, r.right, r.bottom); + _videoLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + public void onGlobalLayout() { + final Rect r = new Rect(); _videoLayout.getWindowVisibleDisplayFrame(r); - mGLView.nativeScreenVisibleRect(r.left, r.top, r.right, r.bottom); - _videoLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - public void onGlobalLayout() { - final Rect r = new Rect(); - _videoLayout.getWindowVisibleDisplayFrame(r); - final int heightDiff = _videoLayout.getRootView().getHeight() - _videoLayout.getHeight(); // Take system bar into consideration - final int widthDiff = _videoLayout.getRootView().getWidth() - _videoLayout.getWidth(); // Nexus 5 has system bar at the right side - Log.v("SDL", "Main window visible region changed: " + r.left + ":" + r.top + ":" + r.width() + ":" + r.height()); - _videoLayout.postDelayed(new Runnable() { - public void run() { - DimSystemStatusBar.get().dim(_videoLayout); - mGLView.nativeScreenVisibleRect(r.left + widthDiff, r.top + heightDiff, r.width(), r.height()); - } - }, 300); - _videoLayout.postDelayed(new Runnable() { - public void run() { - DimSystemStatusBar.get().dim(_videoLayout); - mGLView.nativeScreenVisibleRect(r.left + widthDiff, r.top + heightDiff, r.width(), r.height()); - } - }, 600); - } - }); - } - - @Override - protected void onPause() { - _isPaused = true; - if (mGLView != null) - mGLView.onPause(); - //if( _ad.getView() != null ) - // _ad.getView().onPause(); - super.onPause(); - } - - @Override - protected void onResume() { - super.onResume(); - if (mGLView != null) { + final int heightDiff = _videoLayout.getRootView().getHeight() - _videoLayout.getHeight(); // Take system bar into consideration + final int widthDiff = _videoLayout.getRootView().getWidth() - _videoLayout.getWidth(); // Nexus 5 has system bar at the right side + Log.v("SDL", "Main window visible region changed: " + r.left + ":" + r.top + ":" + r.width() + ":" + r.height()); + _videoLayout.postDelayed(new Runnable() { + public void run() { DimSystemStatusBar.get().dim(_videoLayout); - //DimSystemStatusBar.get().dim(mGLView); - mGLView.onResume(); - } - //if( _ad.getView() != null ) - // _ad.getView().onResume(); - _isPaused = false; - // Nvidia is too smart to use Samsung's stylus API, obviously they need their own method to enable hover events - Intent i = new Intent("com.nvidia.intent.action.ENABLE_STYLUS"); - i.putExtra("package", getPackageName()); - sendBroadcast(i); + mGLView.nativeScreenVisibleRect(r.left + widthDiff, r.top + heightDiff, r.width(), r.height()); + } + }, 300); + _videoLayout.postDelayed(new Runnable() { + public void run() { + DimSystemStatusBar.get().dim(_videoLayout); + mGLView.nativeScreenVisibleRect(r.left + widthDiff, r.top + heightDiff, r.width(), r.height()); + } + }, 600); + } + }); + } + + @Override + protected void onPause() { + _isPaused = true; + if (mGLView != null) + mGLView.onPause(); + //if( _ad.getView() != null ) + // _ad.getView().onPause(); + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + if (mGLView != null) { + DimSystemStatusBar.get().dim(_videoLayout); + //DimSystemStatusBar.get().dim(mGLView); + mGLView.onResume(); } + //if( _ad.getView() != null ) + // _ad.getView().onResume(); + _isPaused = false; + // Nvidia is too smart to use Samsung's stylus API, obviously they need their own method to enable hover events + Intent i = new Intent("com.nvidia.intent.action.ENABLE_STYLUS"); + i.putExtra("package", getPackageName()); + sendBroadcast(i); + } - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - Log.i("SDL", "libSDL: onWindowFocusChanged: " + hasFocus + " - sending onPause/onResume"); - if (!hasFocus) - onPause(); - else - onResume(); + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + Log.i("SDL", "libSDL: onWindowFocusChanged: " + hasFocus + " - sending onPause/onResume"); + if (!hasFocus) + onPause(); + else + onResume(); + } + + public boolean isPaused() { + return _isPaused; + } + + @Override + protected void onDestroy() { + if (mGLView != null) + mGLView.exitApp(); + super.onDestroy(); + try { + Thread.sleep(2000); // The event is sent asynchronously, allow app to save it's state, and call exit() itself. + } catch (InterruptedException e) { } + System.exit(0); + } - public boolean isPaused() { - return _isPaused; - } + @Override + protected void onStart() { + super.onStart(); + } - @Override - protected void onDestroy() { - if (mGLView != null) - mGLView.exitApp(); - super.onDestroy(); - try { - Thread.sleep(2000); // The event is sent asynchronously, allow app to save it's state, and call exit() itself. - } catch (InterruptedException e) { - } - System.exit(0); - } + @Override + protected void onStop() { + super.onStop(); + } - @Override - protected void onStart() { - super.onStart(); - } + @Override + public void onActivityResult(int request, int response, Intent data) { + super.onActivityResult(request, response, data); + } - @Override - protected void onStop() { - super.onStop(); - } + private int TextInputKeyboardList[][] = + { + {0, R.xml.qwerty, R.xml.c64, R.xml.amiga, R.xml.atari800}, + {0, R.xml.qwerty_shift, R.xml.c64, R.xml.amiga_shift, R.xml.atari800}, + {0, R.xml.qwerty_alt, R.xml.c64, R.xml.amiga_alt, R.xml.atari800}, + {0, R.xml.qwerty_alt_shift, R.xml.c64, R.xml.amiga_alt_shift, R.xml.atari800} + }; - @Override - public void onActivityResult(int request, int response, Intent data) { - super.onActivityResult(request, response, data); - } + @Override + public Context getContext() { + return this; + } - private int TextInputKeyboardList[][] = - { - {0, R.xml.qwerty, R.xml.c64, R.xml.amiga, R.xml.atari800}, - {0, R.xml.qwerty_shift, R.xml.c64, R.xml.amiga_shift, R.xml.atari800}, - {0, R.xml.qwerty_alt, R.xml.c64, R.xml.amiga_alt, R.xml.atari800}, - {0, R.xml.qwerty_alt_shift, R.xml.c64, R.xml.amiga_alt_shift, R.xml.atari800} - }; + @Override + public boolean isKeyboardWithoutTextInputShown() { + return keyboardWithoutTextInputShown; + } - @Override - public Context getContext() { - return this; - } + public void showScreenKeyboardWithoutTextInputField(final int keyboard) { + if (!isKeyboardWithoutTextInputShown()) { + keyboardWithoutTextInputShown = true; + runOnUiThread(new Runnable() { + public void run() { + if (keyboard == 0) { + _inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + _inputManager.showSoftInput(mGLView, InputMethodManager.SHOW_FORCED); + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + } else { + if (_screenKeyboard != null) + return; + class BuiltInKeyboardView extends KeyboardView { + public boolean shift = false; + public boolean alt = false; + public TreeSet stickyKeys = new TreeSet(); - @Override - public boolean isKeyboardWithoutTextInputShown() { - return keyboardWithoutTextInputShown; - } + public BuiltInKeyboardView(Context context, android.util.AttributeSet attrs) { + super(context, attrs); + } - public void showScreenKeyboardWithoutTextInputField(final int keyboard) { - if (!isKeyboardWithoutTextInputShown()) { - keyboardWithoutTextInputShown = true; - runOnUiThread(new Runnable() { - public void run() { - if (keyboard == 0) { - _inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); - _inputManager.showSoftInput(mGLView, InputMethodManager.SHOW_FORCED); - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } else { - if (_screenKeyboard != null) - return; - class BuiltInKeyboardView extends KeyboardView { - public boolean shift = false; - public boolean alt = false; - public TreeSet stickyKeys = new TreeSet(); - - public BuiltInKeyboardView(Context context, android.util.AttributeSet attrs) { - super(context, attrs); - } - - public boolean dispatchTouchEvent(final MotionEvent ev) { - if (ev.getY() < getTop()) - return false; - if (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_MOVE) { - // Convert pointer coords, this will lose multitiouch data, however KeyboardView does not support multitouch anyway - MotionEvent converted = MotionEvent.obtain(ev.getDownTime(), ev.getEventTime(), ev.getAction(), ev.getX(), ev.getY() - (float) getTop(), ev.getMetaState()); - return super.dispatchTouchEvent(converted); - } - return false; - } - - public boolean onKeyDown(int key, final KeyEvent event) { - return false; - } - - public boolean onKeyUp(int key, final KeyEvent event) { - return false; - } - - public void ChangeKeyboard() { - int idx = (shift ? 1 : 0) + (alt ? 2 : 0); - setKeyboard(new Keyboard(MainActivity.this, TextInputKeyboardList[idx][keyboard])); - setPreviewEnabled(false); - setProximityCorrectionEnabled(false); - for (Keyboard.Key k : getKeyboard().getKeys()) { - if (stickyKeys.contains(k.codes[0])) { - k.on = true; - invalidateAllKeys(); - } - } - } - } - final BuiltInKeyboardView builtinKeyboard = new BuiltInKeyboardView(MainActivity.this, null); - builtinKeyboard.setAlpha(0.7f); - builtinKeyboard.ChangeKeyboard(); - builtinKeyboard.setOnKeyboardActionListener(new KeyboardView.OnKeyboardActionListener() { - public void onPress(int key) { - if (key == KeyEvent.KEYCODE_BACK) - return; - if (key < 0) - return; - for (Keyboard.Key k : builtinKeyboard.getKeyboard().getKeys()) { - if (k.sticky && key == k.codes[0]) - return; - } - if (key > 100000) { - key -= 100000; - MainActivity.this.onKeyDown(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT)); - } - MainActivity.this.onKeyDown(key, new KeyEvent(KeyEvent.ACTION_DOWN, key)); - } - - public void onRelease(int key) { - if (key == KeyEvent.KEYCODE_BACK) { - builtinKeyboard.setOnKeyboardActionListener(null); - showScreenKeyboardWithoutTextInputField(0); // Hide keyboard - return; - } - if (key == Keyboard.KEYCODE_SHIFT) { - builtinKeyboard.shift = !builtinKeyboard.shift; - if (builtinKeyboard.shift && !builtinKeyboard.alt) - MainActivity.this.onKeyDown(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT)); - else - MainActivity.this.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT)); - builtinKeyboard.ChangeKeyboard(); - return; - } - if (key == Keyboard.KEYCODE_ALT) { - builtinKeyboard.alt = !builtinKeyboard.alt; - if (builtinKeyboard.alt) - MainActivity.this.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT)); - else - builtinKeyboard.shift = false; - builtinKeyboard.ChangeKeyboard(); - return; - } - if (key < 0) - return; - for (Keyboard.Key k : builtinKeyboard.getKeyboard().getKeys()) { - if (k.sticky && key == k.codes[0]) { - if (k.on) { - builtinKeyboard.stickyKeys.add(key); - MainActivity.this.onKeyDown(key, new KeyEvent(KeyEvent.ACTION_DOWN, key)); - } else { - builtinKeyboard.stickyKeys.remove(key); - MainActivity.this.onKeyUp(key, new KeyEvent(KeyEvent.ACTION_UP, key)); - } - return; - } - } - - boolean shifted = false; - if (key > 100000) { - key -= 100000; - shifted = true; - } - - MainActivity.this.onKeyUp(key, new KeyEvent(KeyEvent.ACTION_UP, key)); - - if (shifted) { - MainActivity.this.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT)); - builtinKeyboard.stickyKeys.remove(KeyEvent.KEYCODE_SHIFT_LEFT); - for (Keyboard.Key k : builtinKeyboard.getKeyboard().getKeys()) { - if (k.sticky && k.codes[0] == KeyEvent.KEYCODE_SHIFT_LEFT && k.on) { - k.on = false; - builtinKeyboard.invalidateAllKeys(); - } - } - } - } - - public void onText(CharSequence p1) { - } - - public void swipeLeft() { - } - - public void swipeRight() { - } - - public void swipeDown() { - } - - public void swipeUp() { - } - - public void onKey(int p1, int[] p2) { - } - }); - _screenKeyboard = builtinKeyboard; - FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM); - _videoLayout.addView(_screenKeyboard, layout); - } + public boolean dispatchTouchEvent(final MotionEvent ev) { + if (ev.getY() < getTop()) + return false; + if (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_MOVE) { + // Convert pointer coords, this will lose multitiouch data, however KeyboardView does not support multitouch anyway + MotionEvent converted = MotionEvent.obtain(ev.getDownTime(), ev.getEventTime(), ev.getAction(), ev.getX(), ev.getY() - (float) getTop(), ev.getMetaState()); + return super.dispatchTouchEvent(converted); } - }); - } else { - keyboardWithoutTextInputShown = false; - runOnUiThread(new Runnable() { - public void run() { - if (_screenKeyboard != null) { - _videoLayout.removeView(_screenKeyboard); - _screenKeyboard = null; - } - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); - _inputManager.hideSoftInputFromWindow(mGLView.getWindowToken(), 0); - DimSystemStatusBar.get().dim(_videoLayout); - //DimSystemStatusBar.get().dim(mGLView); + return false; + } + + public boolean onKeyDown(int key, final KeyEvent event) { + return false; + } + + public boolean onKeyUp(int key, final KeyEvent event) { + return false; + } + + public void ChangeKeyboard() { + int idx = (shift ? 1 : 0) + (alt ? 2 : 0); + setKeyboard(new Keyboard(MainActivity.this, TextInputKeyboardList[idx][keyboard])); + setPreviewEnabled(false); + setProximityCorrectionEnabled(false); + for (Keyboard.Key k : getKeyboard().getKeys()) { + if (stickyKeys.contains(k.codes[0])) { + k.on = true; + invalidateAllKeys(); + } } - }); - } - mGLView.nativeScreenKeyboardShown(keyboardWithoutTextInputShown ? 1 : 0); - } - - public void showScreenKeyboard(final String oldText) { - if (Globals.CompatibilityHacksTextInputEmulatesHwKeyboard) { - showScreenKeyboardWithoutTextInputField(Globals.TextInputKeyboard); - return; - } - if (_screenKeyboard != null) - return; - class simpleKeyListener implements OnKeyListener { - MainActivity _parent; - - simpleKeyListener(MainActivity parent) { - _parent = parent; + } } - - ; - - public boolean onKey(View v, int keyCode, KeyEvent event) { - if ((event.getAction() == KeyEvent.ACTION_UP) && ( - keyCode == KeyEvent.KEYCODE_ENTER || - keyCode == KeyEvent.KEYCODE_BACK || - keyCode == KeyEvent.KEYCODE_MENU || - keyCode == KeyEvent.KEYCODE_BUTTON_A || - keyCode == KeyEvent.KEYCODE_BUTTON_B || - keyCode == KeyEvent.KEYCODE_BUTTON_X || - keyCode == KeyEvent.KEYCODE_BUTTON_Y || - keyCode == KeyEvent.KEYCODE_BUTTON_1 || - keyCode == KeyEvent.KEYCODE_BUTTON_2 || - keyCode == KeyEvent.KEYCODE_BUTTON_3 || - keyCode == KeyEvent.KEYCODE_BUTTON_4)) { - _parent.hideScreenKeyboard(); - return true; + final BuiltInKeyboardView builtinKeyboard = new BuiltInKeyboardView(MainActivity.this, null); + builtinKeyboard.setAlpha(0.7f); + builtinKeyboard.ChangeKeyboard(); + builtinKeyboard.setOnKeyboardActionListener(new KeyboardView.OnKeyboardActionListener() { + public void onPress(int key) { + if (key == KeyEvent.KEYCODE_BACK) + return; + if (key < 0) + return; + for (Keyboard.Key k : builtinKeyboard.getKeyboard().getKeys()) { + if (k.sticky && key == k.codes[0]) + return; } + if (key > 100000) { + key -= 100000; + MainActivity.this.onKeyDown(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT)); + } + MainActivity.this.onKeyDown(key, new KeyEvent(KeyEvent.ACTION_DOWN, key)); + } + + public void onRelease(int key) { + if (key == KeyEvent.KEYCODE_BACK) { + builtinKeyboard.setOnKeyboardActionListener(null); + showScreenKeyboardWithoutTextInputField(0); // Hide keyboard + return; + } + if (key == Keyboard.KEYCODE_SHIFT) { + builtinKeyboard.shift = !builtinKeyboard.shift; + if (builtinKeyboard.shift && !builtinKeyboard.alt) + MainActivity.this.onKeyDown(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT)); + else + MainActivity.this.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT)); + builtinKeyboard.ChangeKeyboard(); + return; + } + if (key == Keyboard.KEYCODE_ALT) { + builtinKeyboard.alt = !builtinKeyboard.alt; + if (builtinKeyboard.alt) + MainActivity.this.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT)); + else + builtinKeyboard.shift = false; + builtinKeyboard.ChangeKeyboard(); + return; + } + if (key < 0) + return; + for (Keyboard.Key k : builtinKeyboard.getKeyboard().getKeys()) { + if (k.sticky && key == k.codes[0]) { + if (k.on) { + builtinKeyboard.stickyKeys.add(key); + MainActivity.this.onKeyDown(key, new KeyEvent(KeyEvent.ACTION_DOWN, key)); + } else { + builtinKeyboard.stickyKeys.remove(key); + MainActivity.this.onKeyUp(key, new KeyEvent(KeyEvent.ACTION_UP, key)); + } + return; + } + } + + boolean shifted = false; + if (key > 100000) { + key -= 100000; + shifted = true; + } + + MainActivity.this.onKeyUp(key, new KeyEvent(KeyEvent.ACTION_UP, key)); + + if (shifted) { + MainActivity.this.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT)); + builtinKeyboard.stickyKeys.remove(KeyEvent.KEYCODE_SHIFT_LEFT); + for (Keyboard.Key k : builtinKeyboard.getKeyboard().getKeys()) { + if (k.sticky && k.codes[0] == KeyEvent.KEYCODE_SHIFT_LEFT && k.on) { + k.on = false; + builtinKeyboard.invalidateAllKeys(); + } + } + } + } + + public void onText(CharSequence p1) { + } + + public void swipeLeft() { + } + + public void swipeRight() { + } + + public void swipeDown() { + } + + public void swipeUp() { + } + + public void onKey(int p1, int[] p2) { + } + }); + _screenKeyboard = builtinKeyboard; + FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM); + _videoLayout.addView(_screenKeyboard, layout); + } + } + }); + } else { + keyboardWithoutTextInputShown = false; + runOnUiThread(new Runnable() { + public void run() { + if (_screenKeyboard != null) { + _videoLayout.removeView(_screenKeyboard); + _screenKeyboard = null; + } + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + _inputManager.hideSoftInputFromWindow(mGLView.getWindowToken(), 0); + DimSystemStatusBar.get().dim(_videoLayout); + //DimSystemStatusBar.get().dim(mGLView); + } + }); + } + mGLView.nativeScreenKeyboardShown(keyboardWithoutTextInputShown ? 1 : 0); + } + + public void showScreenKeyboard(final String oldText) { + if (Globals.CompatibilityHacksTextInputEmulatesHwKeyboard) { + showScreenKeyboardWithoutTextInputField(Globals.TextInputKeyboard); + return; + } + if (_screenKeyboard != null) + return; + class simpleKeyListener implements OnKeyListener { + MainActivity _parent; + + simpleKeyListener(MainActivity parent) { + _parent = parent; + } + + ; + + public boolean onKey(View v, int keyCode, KeyEvent event) { + if ((event.getAction() == KeyEvent.ACTION_UP) && ( + keyCode == KeyEvent.KEYCODE_ENTER || + keyCode == KeyEvent.KEYCODE_BACK || + keyCode == KeyEvent.KEYCODE_MENU || + keyCode == KeyEvent.KEYCODE_BUTTON_A || + keyCode == KeyEvent.KEYCODE_BUTTON_B || + keyCode == KeyEvent.KEYCODE_BUTTON_X || + keyCode == KeyEvent.KEYCODE_BUTTON_Y || + keyCode == KeyEvent.KEYCODE_BUTTON_1 || + keyCode == KeyEvent.KEYCODE_BUTTON_2 || + keyCode == KeyEvent.KEYCODE_BUTTON_3 || + keyCode == KeyEvent.KEYCODE_BUTTON_4)) { + _parent.hideScreenKeyboard(); + return true; + } /* if (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_CLEAR) { @@ -706,104 +690,104 @@ public class MainActivity extends AppCompatActivity implements NeoXorgViewClient } } */ - //Log.i("SDL", "Key " + keyCode + " flags " + event.getFlags() + " action " + event.getAction()); - return false; - } - } - ; - EditText screenKeyboard = new EditText(this, null, - android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP ? android.R.style.TextAppearance_Material_Widget_EditText : android.R.style.TextAppearance_Widget_EditText); - String hint = _screenKeyboardHintMessage; - screenKeyboard.setHint(hint != null ? hint : getString(R.string.text_edit_click_here)); - screenKeyboard.setText(oldText); - screenKeyboard.setSelection(screenKeyboard.getText().length()); - screenKeyboard.setOnKeyListener(new simpleKeyListener(this)); - screenKeyboard.setBackgroundColor(this.getResources().getColor(android.R.color.primary_text_light)); - screenKeyboard.setTextColor(this.getResources().getColor(android.R.color.background_light)); - if (isRunningOnOUYA() && Globals.TvBorders) - screenKeyboard.setPadding(100, 100, 100, 100); // Bad bad HDMI TVs all have cropped borders - _screenKeyboard = screenKeyboard; - _videoLayout.addView(_screenKeyboard); - //_screenKeyboard.setKeyListener(new TextKeyListener(TextKeyListener.Capitalize.NONE, false)); - screenKeyboard.setInputType(InputType.TYPE_CLASS_TEXT); - screenKeyboard.setFocusableInTouchMode(true); - screenKeyboard.setFocusable(true); - //_inputManager.showSoftInput(screenKeyboard, InputMethodManager.SHOW_IMPLICIT); - //getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - // Hack to try to force on-screen keyboard - final EditText keyboard = screenKeyboard; + //Log.i("SDL", "Key " + keyCode + " flags " + event.getFlags() + " action " + event.getAction()); + return false; + } + } + ; + EditText screenKeyboard = new EditText(this, null, + android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP ? android.R.style.TextAppearance_Material_Widget_EditText : android.R.style.TextAppearance_Widget_EditText); + String hint = _screenKeyboardHintMessage; + screenKeyboard.setHint(hint != null ? hint : getString(R.string.text_edit_click_here)); + screenKeyboard.setText(oldText); + screenKeyboard.setSelection(screenKeyboard.getText().length()); + screenKeyboard.setOnKeyListener(new simpleKeyListener(this)); + screenKeyboard.setBackgroundColor(this.getResources().getColor(android.R.color.primary_text_light)); + screenKeyboard.setTextColor(this.getResources().getColor(android.R.color.background_light)); + if (isRunningOnOUYA() && Globals.TvBorders) + screenKeyboard.setPadding(100, 100, 100, 100); // Bad bad HDMI TVs all have cropped borders + _screenKeyboard = screenKeyboard; + _videoLayout.addView(_screenKeyboard); + //_screenKeyboard.setKeyListener(new TextKeyListener(TextKeyListener.Capitalize.NONE, false)); + screenKeyboard.setInputType(InputType.TYPE_CLASS_TEXT); + screenKeyboard.setFocusableInTouchMode(true); + screenKeyboard.setFocusable(true); + //_inputManager.showSoftInput(screenKeyboard, InputMethodManager.SHOW_IMPLICIT); + //getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + // Hack to try to force on-screen keyboard + final EditText keyboard = screenKeyboard; + keyboard.postDelayed(new Runnable() { + public void run() { + keyboard.requestFocus(); + //_inputManager.showSoftInput(keyboard, InputMethodManager.SHOW_FORCED); + // Hack from Stackoverflow, to force text input on Ouya + keyboard.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 0, 0, 0)); + keyboard.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 0, 0, 0)); keyboard.postDelayed(new Runnable() { - public void run() { - keyboard.requestFocus(); - //_inputManager.showSoftInput(keyboard, InputMethodManager.SHOW_FORCED); - // Hack from Stackoverflow, to force text input on Ouya - keyboard.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 0, 0, 0)); - keyboard.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 0, 0, 0)); - keyboard.postDelayed(new Runnable() { - public void run() { - keyboard.requestFocus(); - keyboard.setSelection(keyboard.getText().length()); - } - }, 100); - } - }, 300); + public void run() { + keyboard.requestFocus(); + keyboard.setSelection(keyboard.getText().length()); + } + }, 100); + } + }, 300); + } + + ; + + public void hideScreenKeyboard() { + if (keyboardWithoutTextInputShown) + showScreenKeyboardWithoutTextInputField(Globals.TextInputKeyboard); + + if (_screenKeyboard == null || !(_screenKeyboard instanceof EditText)) + return; + + synchronized (textInput) { + String text = ((EditText) _screenKeyboard).getText().toString(); + for (int i = 0; i < text.length(); i++) { + DemoRenderer.nativeTextInput((int) text.charAt(i), (int) text.codePointAt(i)); + } } + DemoRenderer.nativeTextInputFinished(); + _inputManager.hideSoftInputFromWindow(_screenKeyboard.getWindowToken(), 0); + _videoLayout.removeView(_screenKeyboard); + _screenKeyboard = null; + mGLView.setFocusableInTouchMode(true); + mGLView.setFocusable(true); + mGLView.requestFocus(); + DimSystemStatusBar.get().dim(_videoLayout); - ; - - public void hideScreenKeyboard() { - if (keyboardWithoutTextInputShown) - showScreenKeyboardWithoutTextInputField(Globals.TextInputKeyboard); - - if (_screenKeyboard == null || !(_screenKeyboard instanceof EditText)) - return; - - synchronized (textInput) { - String text = ((EditText) _screenKeyboard).getText().toString(); - for (int i = 0; i < text.length(); i++) { - DemoRenderer.nativeTextInput((int) text.charAt(i), (int) text.codePointAt(i)); - } - } - DemoRenderer.nativeTextInputFinished(); - _inputManager.hideSoftInputFromWindow(_screenKeyboard.getWindowToken(), 0); - _videoLayout.removeView(_screenKeyboard); - _screenKeyboard = null; - mGLView.setFocusableInTouchMode(true); - mGLView.setFocusable(true); - mGLView.requestFocus(); + _videoLayout.postDelayed(new Runnable() { + public void run() { DimSystemStatusBar.get().dim(_videoLayout); + } + }, 500); + } - _videoLayout.postDelayed(new Runnable() { - public void run() { - DimSystemStatusBar.get().dim(_videoLayout); - } - }, 500); - } + ; - ; + public boolean isScreenKeyboardShown() { + return _screenKeyboard != null; + } - public boolean isScreenKeyboardShown() { - return _screenKeyboard != null; - } + ; - ; + public void setScreenKeyboardHintMessage(String s) { + _screenKeyboardHintMessage = s; + //Log.i("SDL", "setScreenKeyboardHintMessage: " + (_screenKeyboardHintMessage != null ? _screenKeyboardHintMessage : getString(R.string.text_edit_click_here))); + runOnUiThread(new Runnable() { + public void run() { + if (_screenKeyboard != null && _screenKeyboard instanceof EditText) { + String hint = _screenKeyboardHintMessage; + ((EditText) _screenKeyboard).setHint(hint != null ? hint : getString(R.string.text_edit_click_here)); + } + } + }); + } - public void setScreenKeyboardHintMessage(String s) { - _screenKeyboardHintMessage = s; - //Log.i("SDL", "setScreenKeyboardHintMessage: " + (_screenKeyboardHintMessage != null ? _screenKeyboardHintMessage : getString(R.string.text_edit_click_here))); - runOnUiThread(new Runnable() { - public void run() { - if (_screenKeyboard != null && _screenKeyboard instanceof EditText) { - String hint = _screenKeyboardHintMessage; - ((EditText) _screenKeyboard).setHint(hint != null ? hint : getString(R.string.text_edit_click_here)); - } - } - }); - } - - final static int ADVERTISEMENT_POSITION_RIGHT = -1; - final static int ADVERTISEMENT_POSITION_BOTTOM = -1; - final static int ADVERTISEMENT_POSITION_CENTER = -2; + final static int ADVERTISEMENT_POSITION_RIGHT = -1; + final static int ADVERTISEMENT_POSITION_BOTTOM = -1; + final static int ADVERTISEMENT_POSITION_CENTER = -2; /* @Override @@ -878,233 +862,233 @@ public class MainActivity extends AppCompatActivity implements NeoXorgViewClient } */ - //private Configuration oldConfig = null; - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - updateScreenOrientation(); + //private Configuration oldConfig = null; + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateScreenOrientation(); + } + + public void updateScreenOrientation() { + int rotation = Surface.ROTATION_0; + rotation = getWindowManager().getDefaultDisplay().getRotation(); + AccelerometerReader.gyro.invertedOrientation = (rotation == Surface.ROTATION_180 || rotation == Surface.ROTATION_270); + //Log.d("SDL", "updateScreenOrientation(): screen orientation: " + rotation + " inverted " + AccelerometerReader.gyro.invertedOrientation); + } + + @Override + public void initScreenOrientation() { + setScreenOrientation(); + } + + public void setText(final String t) { + class Callback implements Runnable { + MainActivity Parent; + public SpannedString text; + + public void run() { + Parent.setUpStatusLabel(); + if (Parent._tv != null) + Parent._tv.setText(text); + } + } + Callback cb = new Callback(); + cb.text = new SpannedString(t); + cb.Parent = this; + this.runOnUiThread(cb); + } + + @Override + public void onNewIntent(Intent i) { + Log.i("SDL", "onNewIntent(): " + i.toString()); + super.onNewIntent(i); + setIntent(i); + } + + @SuppressLint("UnsafeDynamicallyLoadedCode") + public void LoadLibraries() { + try { + if (Globals.NeedGles3) { + System.loadLibrary("GLESv3"); + Log.i("SDL", "libSDL: loaded GLESv3 lib"); + } else if (Globals.NeedGles2) { + System.loadLibrary("GLESv2"); + Log.i("SDL", "libSDL: loaded GLESv2 lib"); + } + } catch (UnsatisfiedLinkError e) { + Log.i("SDL", "libSDL: Cannot load GLESv3 or GLESv2 lib"); } - public void updateScreenOrientation() { - int rotation = Surface.ROTATION_0; - rotation = getWindowManager().getDefaultDisplay().getRotation(); - AccelerometerReader.gyro.invertedOrientation = (rotation == Surface.ROTATION_180 || rotation == Surface.ROTATION_270); - //Log.d("SDL", "updateScreenOrientation(): screen orientation: " + rotation + " inverted " + AccelerometerReader.gyro.invertedOrientation); - } - - @Override - public void initScreenOrientation() { - setScreenOrientation(); - } - - public void setText(final String t) { - class Callback implements Runnable { - MainActivity Parent; - public SpannedString text; - - public void run() { - Parent.setUpStatusLabel(); - if (Parent._tv != null) - Parent._tv.setText(text); - } - } - Callback cb = new Callback(); - cb.text = new SpannedString(t); - cb.Parent = this; - this.runOnUiThread(cb); - } - - @Override - public void onNewIntent(Intent i) { - Log.i("SDL", "onNewIntent(): " + i.toString()); - super.onNewIntent(i); - setIntent(i); - } - - @SuppressLint("UnsafeDynamicallyLoadedCode") - public void LoadLibraries() { + // Load all libraries + try { + for (String libname : Globals.XLIBS) { + String soPath = Globals.XLIB_DIR + libname; + Log.i("SDL", "libSDL: loading lib " + soPath); try { - if (Globals.NeedGles3) { - System.loadLibrary("GLESv3"); - Log.i("SDL", "libSDL: loaded GLESv3 lib"); - } else if (Globals.NeedGles2) { - System.loadLibrary("GLESv2"); - Log.i("SDL", "libSDL: loaded GLESv2 lib"); - } - } catch (UnsatisfiedLinkError e) { - Log.i("SDL", "libSDL: Cannot load GLESv3 or GLESv2 lib"); + System.load(soPath); + } catch (UnsatisfiedLinkError error) { + Log.i("SDL", "libSDL: error loading lib " + soPath + + ", reason: " + error.getLocalizedMessage()); } + } + } catch (UnsatisfiedLinkError ignore) { + } + } - // Load all libraries + @SuppressLint("UnsafeDynamicallyLoadedCode") + public static void LoadApplicationLibrary(final Context context) { + Settings.nativeChdir(Globals.DataDir); + try { + for (String libname : Globals.XAPP_LIBS) { + String soPath = Globals.XLIB_DIR + libname; + Log.i("SDL", "libSDL: loading lib " + soPath); try { - for (String libname : Globals.XLIBS) { - String soPath = Globals.XLIB_DIR + libname; - Log.i("SDL", "libSDL: loading lib " + soPath); - try { - System.load(soPath); - } catch (UnsatisfiedLinkError error) { - Log.i("SDL", "libSDL: error loading lib " + soPath - + ", reason: " + error.getLocalizedMessage()); - } - } - } catch (UnsatisfiedLinkError ignore) { + System.load(soPath); + } catch (UnsatisfiedLinkError error) { + Log.i("SDL", "libSDL: error loading lib " + soPath + + ", reason: " + error.getLocalizedMessage()); } + } + } catch (UnsatisfiedLinkError ignore) { } + Log.v("SDL", "libSDL: loaded all libraries"); + ApplicationLibraryLoaded = true; + } - @SuppressLint("UnsafeDynamicallyLoadedCode") - public static void LoadApplicationLibrary(final Context context) { - Settings.nativeChdir(Globals.DataDir); - try { - for (String libname : Globals.XAPP_LIBS) { - String soPath = Globals.XLIB_DIR + libname; - Log.i("SDL", "libSDL: loading lib " + soPath); - try { - System.load(soPath); - } catch (UnsatisfiedLinkError error) { - Log.i("SDL", "libSDL: error loading lib " + soPath - + ", reason: " + error.getLocalizedMessage()); - } - } - } catch (UnsatisfiedLinkError ignore) { - } - Log.v("SDL", "libSDL: loaded all libraries"); - ApplicationLibraryLoaded = true; + public int getApplicationVersion() { + try { + PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); + return packageInfo.versionCode; + } catch (PackageManager.NameNotFoundException e) { + Log.i("SDL", "libSDL: Cannot get the version of our own package: " + e); } + return 0; + } - public int getApplicationVersion() { - try { - PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); - return packageInfo.versionCode; - } catch (PackageManager.NameNotFoundException e) { - Log.i("SDL", "libSDL: Cannot get the version of our own package: " + e); - } - return 0; + public boolean isRunningOnOUYA() { + try { + PackageInfo packageInfo = getPackageManager().getPackageInfo("tv.ouya", 0); + return true; + } catch (PackageManager.NameNotFoundException e) { } + UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE); + return (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) || Globals.OuyaEmulation; + } - public boolean isRunningOnOUYA() { - try { - PackageInfo packageInfo = getPackageManager().getPackageInfo("tv.ouya", 0); - return true; - } catch (PackageManager.NameNotFoundException e) { - } - UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE); - return (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) || Globals.OuyaEmulation; + @Override + public NeoGLView getGLView() { + return mGLView; + } + + public boolean isCurrentOrientationHorizontal() { + if (Globals.AutoDetectOrientation) { + // Less reliable way to detect orientation, but works with multiwindow + View topView = getWindow().peekDecorView(); + if (topView != null) { + //Log.d("SDL", "isCurrentOrientationHorizontal(): decorview: " + topView.getWidth() + "x" + topView.getHeight()); + return topView.getWidth() >= topView.getHeight(); + } } + Display getOrient = getWindowManager().getDefaultDisplay(); + return getOrient.getWidth() >= getOrient.getHeight(); + } - @Override - public NeoGLView getGLView() { - return mGLView; + void setScreenOrientation() { + Globals.AutoDetectOrientation = true; + if (Globals.AutoDetectOrientation) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER); + return; } + setRequestedOrientation(Globals.HorizontalOrientation ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); + } - public boolean isCurrentOrientationHorizontal() { - if (Globals.AutoDetectOrientation) { - // Less reliable way to detect orientation, but works with multiwindow - View topView = getWindow().peekDecorView(); - if (topView != null) { - //Log.d("SDL", "isCurrentOrientationHorizontal(): decorview: " + topView.getWidth() + "x" + topView.getHeight()); - return topView.getWidth() >= topView.getHeight(); - } - } - Display getOrient = getWindowManager().getDefaultDisplay(); - return getOrient.getWidth() >= getOrient.getHeight(); + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (permissions.length == 0 || grantResults.length == 0) { + Log.i("SDL", "libSDL: Permission request dialog was aborted"); + return; } - - void setScreenOrientation() { - Globals.AutoDetectOrientation = true; - if (Globals.AutoDetectOrientation) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER); - return; - } - setRequestedOrientation(Globals.HorizontalOrientation ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); + if (Manifest.permission.RECORD_AUDIO.equals(permissions[0])) { + Log.i("SDL", "libSDL: Record audio permission: " + (grantResults[0] == PackageManager.PERMISSION_GRANTED ? "GRANTED" : "DENIED")); } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - if (permissions.length == 0 || grantResults.length == 0) { - Log.i("SDL", "libSDL: Permission request dialog was aborted"); - return; - } - if (Manifest.permission.RECORD_AUDIO.equals(permissions[0])) { - Log.i("SDL", "libSDL: Record audio permission: " + (grantResults[0] == PackageManager.PERMISSION_GRANTED ? "GRANTED" : "DENIED")); - } - if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permissions[0])) { - Log.i("SDL", "libSDL: Write external storage permission: " + (grantResults[0] == PackageManager.PERMISSION_GRANTED ? "GRANTED" : "DENIED")); - writeExternalStoragePermissionDialogAnswered = true; - } + if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permissions[0])) { + Log.i("SDL", "libSDL: Write external storage permission: " + (grantResults[0] == PackageManager.PERMISSION_GRANTED ? "GRANTED" : "DENIED")); + writeExternalStoragePermissionDialogAnswered = true; } + } - public void setSystemMousePointerVisible(int visible) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - mGLView.setPointerIcon(android.view.PointerIcon.getSystemIcon(this, (visible == 0) ? android.view.PointerIcon.TYPE_NULL : android.view.PointerIcon.TYPE_DEFAULT)); - } + public void setSystemMousePointerVisible(int visible) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + mGLView.setPointerIcon(android.view.PointerIcon.getSystemIcon(this, (visible == 0) ? android.view.PointerIcon.TYPE_NULL : android.view.PointerIcon.TYPE_DEFAULT)); } + } - public FrameLayout getVideoLayout() { - return _videoLayout; - } + public FrameLayout getVideoLayout() { + return _videoLayout; + } - NeoGLView mGLView = null; - private static AudioThread mAudioThread = null; + NeoGLView mGLView = null; + private static AudioThread mAudioThread = null; - private TextView _tv = null; - private Button _btn = null; - private LinearLayout _layout = null; - private LinearLayout _layout2 = null; - public ProgressDialog loadingDialog = null; + private TextView _tv = null; + private Button _btn = null; + private LinearLayout _layout = null; + private LinearLayout _layout2 = null; + public ProgressDialog loadingDialog = null; - FrameLayout _videoLayout = null; - private View _screenKeyboard = null; - private String _screenKeyboardHintMessage = null; - static boolean keyboardWithoutTextInputShown = false; - private boolean sdlInited = false; - public static boolean ApplicationLibraryLoaded = false; + FrameLayout _videoLayout = null; + private View _screenKeyboard = null; + private String _screenKeyboardHintMessage = null; + static boolean keyboardWithoutTextInputShown = false; + private boolean sdlInited = false; + public static boolean ApplicationLibraryLoaded = false; - boolean _isPaused = false; - private InputMethodManager _inputManager = null; + boolean _isPaused = false; + private InputMethodManager _inputManager = null; - public LinkedList textInput = new LinkedList(); - public static MainActivity instance = null; - public boolean writeExternalStoragePermissionDialogAnswered = false; + public LinkedList textInput = new LinkedList(); + public static MainActivity instance = null; + public boolean writeExternalStoragePermissionDialogAnswered = false; } // *** HONEYCOMB / ICS FIX FOR FULLSCREEN MODE, by lmak *** abstract class DimSystemStatusBar { - public static DimSystemStatusBar get() { - return DimSystemStatusBarHoneycomb.Holder.sInstance; + public static DimSystemStatusBar get() { + return DimSystemStatusBarHoneycomb.Holder.sInstance; + } + + public abstract void dim(final View view); + + private static class DimSystemStatusBarHoneycomb extends DimSystemStatusBar { + private static class Holder { + private static final DimSystemStatusBarHoneycomb sInstance = new DimSystemStatusBarHoneycomb(); } - public abstract void dim(final View view); - - private static class DimSystemStatusBarHoneycomb extends DimSystemStatusBar { - private static class Holder { - private static final DimSystemStatusBarHoneycomb sInstance = new DimSystemStatusBarHoneycomb(); - } - - public void dim(final View view) { - if (Globals.ImmersiveMode) - // Immersive mode, I already hear curses when system bar reappears mid-game from the slightest swipe at the bottom of the screen - view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN); - else - view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); - } + public void dim(final View view) { + if (Globals.ImmersiveMode) + // Immersive mode, I already hear curses when system bar reappears mid-game from the slightest swipe at the bottom of the screen + view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN); + else + view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); } + } } abstract class SetLayerType { - public static SetLayerType get() { - return SetLayerTypeHoneycomb.Holder.sInstance; + public static SetLayerType get() { + return SetLayerTypeHoneycomb.Holder.sInstance; + } + + public abstract void setLayerType(final View view); + + private static class SetLayerTypeHoneycomb extends SetLayerType { + private static class Holder { + private static final SetLayerTypeHoneycomb sInstance = new SetLayerTypeHoneycomb(); } - public abstract void setLayerType(final View view); - - private static class SetLayerTypeHoneycomb extends SetLayerType { - private static class Holder { - private static final SetLayerTypeHoneycomb sInstance = new SetLayerTypeHoneycomb(); - } - - public void setLayerType(final View view) { - view.setLayerType(View.LAYER_TYPE_NONE, null); - //view.setLayerType(android.view.View.LAYER_TYPE_HARDWARE, null); - } + public void setLayerType(final View view) { + view.setLayerType(View.LAYER_TYPE_NONE, null); + //view.setLayerType(android.view.View.LAYER_TYPE_HARDWARE, null); } + } } diff --git a/Xorg/src/main/java/io/neoterm/NeoAccelerometerReader.java b/Xorg/src/main/java/io/neoterm/NeoAccelerometerReader.java index 92d30fb..cdba3bb 100644 --- a/Xorg/src/main/java/io/neoterm/NeoAccelerometerReader.java +++ b/Xorg/src/main/java/io/neoterm/NeoAccelerometerReader.java @@ -7,11 +7,11 @@ import android.content.Context; */ public class NeoAccelerometerReader extends AccelerometerReader { - public NeoAccelerometerReader(Context context) { - super(context); - } + public NeoAccelerometerReader(Context context) { + super(context); + } - public static void setGyroInvertedOrientation(boolean invertedOrientation) { - gyro.invertedOrientation = invertedOrientation; - } + public static void setGyroInvertedOrientation(boolean invertedOrientation) { + gyro.invertedOrientation = invertedOrientation; + } } diff --git a/Xorg/src/main/java/io/neoterm/NeoAudioThread.java b/Xorg/src/main/java/io/neoterm/NeoAudioThread.java index 49dfc12..6b972b1 100644 --- a/Xorg/src/main/java/io/neoterm/NeoAudioThread.java +++ b/Xorg/src/main/java/io/neoterm/NeoAudioThread.java @@ -7,7 +7,7 @@ import io.neoterm.xorg.NeoXorgViewClient; */ public class NeoAudioThread extends AudioThread { - public NeoAudioThread(NeoXorgViewClient client) { - super(client); - } + public NeoAudioThread(NeoXorgViewClient client) { + super(client); + } } diff --git a/Xorg/src/main/java/io/neoterm/NeoGLView.java b/Xorg/src/main/java/io/neoterm/NeoGLView.java index aaaac0a..b5ee367 100644 --- a/Xorg/src/main/java/io/neoterm/NeoGLView.java +++ b/Xorg/src/main/java/io/neoterm/NeoGLView.java @@ -7,15 +7,15 @@ import io.neoterm.xorg.NeoXorgViewClient; */ public class NeoGLView extends DemoGLSurfaceView { - public NeoGLView(NeoXorgViewClient client) { - super(client); - } + public NeoGLView(NeoXorgViewClient client) { + super(client); + } - public void callNativeScreenKeyboardShown(int shown) { - nativeScreenKeyboardShown(shown); - } + public void callNativeScreenKeyboardShown(int shown) { + nativeScreenKeyboardShown(shown); + } - public void callNativeScreenVisibleRect(int x, int y, int w, int h) { - nativeScreenVisibleRect(x, y, w, h); - } + public void callNativeScreenVisibleRect(int x, int y, int w, int h) { + nativeScreenVisibleRect(x, y, w, h); + } } diff --git a/Xorg/src/main/java/io/neoterm/NeoRenderer.java b/Xorg/src/main/java/io/neoterm/NeoRenderer.java index a69874d..a298ca5 100644 --- a/Xorg/src/main/java/io/neoterm/NeoRenderer.java +++ b/Xorg/src/main/java/io/neoterm/NeoRenderer.java @@ -5,11 +5,11 @@ package io.neoterm; */ public class NeoRenderer { - public static void callNativeTextInputFinished() { - DemoRenderer.nativeTextInputFinished(); - } + public static void callNativeTextInputFinished() { + DemoRenderer.nativeTextInputFinished(); + } - public static void callNativeTextInput(int ascii, int unicode) { - DemoRenderer.nativeTextInput(ascii, unicode); - } + public static void callNativeTextInput(int ascii, int unicode) { + DemoRenderer.nativeTextInput(ascii, unicode); + } } diff --git a/Xorg/src/main/java/io/neoterm/NeoTextInput.java b/Xorg/src/main/java/io/neoterm/NeoTextInput.java index 3ea936e..e2505e1 100644 --- a/Xorg/src/main/java/io/neoterm/NeoTextInput.java +++ b/Xorg/src/main/java/io/neoterm/NeoTextInput.java @@ -7,11 +7,11 @@ import io.neoterm.xorg.R; */ public class NeoTextInput { - public static int TextInputKeyboardList[][] = - { - {0, R.xml.qwerty, R.xml.c64, R.xml.amiga, R.xml.atari800}, - {0, R.xml.qwerty_shift, R.xml.c64, R.xml.amiga_shift, R.xml.atari800}, - {0, R.xml.qwerty_alt, R.xml.c64, R.xml.amiga_alt, R.xml.atari800}, - {0, R.xml.qwerty_alt_shift, R.xml.c64, R.xml.amiga_alt_shift, R.xml.atari800} - }; + public static int TextInputKeyboardList[][] = + { + {0, R.xml.qwerty, R.xml.c64, R.xml.amiga, R.xml.atari800}, + {0, R.xml.qwerty_shift, R.xml.c64, R.xml.amiga_shift, R.xml.atari800}, + {0, R.xml.qwerty_alt, R.xml.c64, R.xml.amiga_alt, R.xml.atari800}, + {0, R.xml.qwerty_alt_shift, R.xml.c64, R.xml.amiga_alt_shift, R.xml.atari800} + }; } diff --git a/Xorg/src/main/java/io/neoterm/NeoXorgSettings.java b/Xorg/src/main/java/io/neoterm/NeoXorgSettings.java index a7d82c4..248efbb 100644 --- a/Xorg/src/main/java/io/neoterm/NeoXorgSettings.java +++ b/Xorg/src/main/java/io/neoterm/NeoXorgSettings.java @@ -7,7 +7,7 @@ import io.neoterm.xorg.NeoXorgViewClient; */ public class NeoXorgSettings { - public static void init(NeoXorgViewClient client) { - Settings.Load(client); - } + public static void init(NeoXorgViewClient client) { + Settings.Load(client); + } } diff --git a/Xorg/src/main/java/io/neoterm/Settings.java b/Xorg/src/main/java/io/neoterm/Settings.java index 0bfec86..e94249b 100644 --- a/Xorg/src/main/java/io/neoterm/Settings.java +++ b/Xorg/src/main/java/io/neoterm/Settings.java @@ -28,525 +28,517 @@ import android.os.Environment; import android.os.StatFs; import android.util.DisplayMetrics; import android.util.Log; +import io.neoterm.xorg.NeoXorgViewClient; +import io.neoterm.xorg.R; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; +import java.io.*; import java.util.ArrayList; import java.util.Locale; import java.util.zip.GZIPInputStream; -import io.neoterm.xorg.NeoXorgViewClient; -import io.neoterm.xorg.R; - // TODO: too much code here, split into multiple files, possibly auto-generated menus? @SuppressWarnings("JniMissingFunction") public class Settings { - static String SettingsFileName = "libsdl-settings.cfg"; + static String SettingsFileName = "libsdl-settings.cfg"; - static boolean settingsLoaded = false; - static boolean settingsChanged = false; - static final int SETTINGS_FILE_VERSION = 5; - static boolean convertButtonSizeFromOldSdlVersion = false; + static boolean settingsLoaded = false; + static boolean settingsChanged = false; + static final int SETTINGS_FILE_VERSION = 5; + static boolean convertButtonSizeFromOldSdlVersion = false; - static void Save(final NeoXorgViewClient p) { - try { - ObjectOutputStream out = new ObjectOutputStream(p.getContext().openFileOutput(SettingsFileName, Context.MODE_PRIVATE)); - out.writeInt(SETTINGS_FILE_VERSION); - out.writeBoolean(Globals.DownloadToSdcard); - out.writeBoolean(Globals.PhoneHasArrowKeys); - out.writeBoolean(false); - out.writeBoolean(Globals.UseAccelerometerAsArrowKeys); - out.writeBoolean(Globals.UseTouchscreenKeyboard); - out.writeInt(Globals.TouchscreenKeyboardSize); - out.writeInt(Globals.AccelerometerSensitivity); - out.writeInt(Globals.AccelerometerCenterPos); - out.writeInt(0); - out.writeInt(Globals.AudioBufferConfig); - out.writeInt(Globals.TouchscreenKeyboardTheme); - out.writeInt(Globals.RightClickMethod); - out.writeInt(Globals.ShowScreenUnderFinger); - out.writeInt(Globals.LeftClickMethod); - out.writeBoolean(Globals.MoveMouseWithJoystick); - out.writeBoolean(Globals.ClickMouseWithDpad); - out.writeInt(Globals.ClickScreenPressure); - out.writeInt(Globals.ClickScreenTouchspotSize); - out.writeBoolean(Globals.KeepAspectRatio); - out.writeInt(Globals.MoveMouseWithJoystickSpeed); - out.writeInt(Globals.MoveMouseWithJoystickAccel); - out.writeInt(SDL_Keys.JAVA_KEYCODE_LAST); - for (int i = 0; i < SDL_Keys.JAVA_KEYCODE_LAST; i++) { - out.writeInt(Globals.RemapHwKeycode[i]); - } - out.writeInt(Globals.RemapScreenKbKeycode.length); - for (int i = 0; i < Globals.RemapScreenKbKeycode.length; i++) { - out.writeInt(Globals.RemapScreenKbKeycode[i]); - } - out.writeInt(Globals.ScreenKbControlsShown.length); - for (int i = 0; i < Globals.ScreenKbControlsShown.length; i++) { - out.writeBoolean(Globals.ScreenKbControlsShown[i]); - } - out.writeInt(Globals.TouchscreenKeyboardTransparency); - out.writeInt(Globals.RemapMultitouchGestureKeycode.length); - for (int i = 0; i < Globals.RemapMultitouchGestureKeycode.length; i++) { - out.writeInt(Globals.RemapMultitouchGestureKeycode[i]); - out.writeBoolean(Globals.MultitouchGesturesUsed[i]); - } - out.writeInt(Globals.MultitouchGestureSensitivity); - for (int i = 0; i < Globals.TouchscreenCalibration.length; i++) - out.writeInt(Globals.TouchscreenCalibration[i]); - out.writeInt(Globals.DataDir.length()); - for (int i = 0; i < Globals.DataDir.length(); i++) - out.writeChar(Globals.DataDir.charAt(i)); - out.writeInt(Globals.CommandLine.length()); - for (int i = 0; i < Globals.CommandLine.length(); i++) - out.writeChar(Globals.CommandLine.charAt(i)); - out.writeInt(Globals.ScreenKbControlsLayout.length); - for (int i = 0; i < Globals.ScreenKbControlsLayout.length; i++) - for (int ii = 0; ii < 4; ii++) - out.writeInt(Globals.ScreenKbControlsLayout[i][ii]); - out.writeInt(Globals.LeftClickKey); - out.writeInt(Globals.RightClickKey); - out.writeBoolean(Globals.VideoLinearFilter); - out.writeInt(Globals.LeftClickTimeout); - out.writeInt(Globals.RightClickTimeout); - out.writeBoolean(Globals.RelativeMouseMovement); - out.writeInt(Globals.RelativeMouseMovementSpeed); - out.writeInt(Globals.RelativeMouseMovementAccel); - out.writeBoolean(Globals.MultiThreadedVideo); + static void Save(final NeoXorgViewClient p) { + try { + ObjectOutputStream out = new ObjectOutputStream(p.getContext().openFileOutput(SettingsFileName, Context.MODE_PRIVATE)); + out.writeInt(SETTINGS_FILE_VERSION); + out.writeBoolean(Globals.DownloadToSdcard); + out.writeBoolean(Globals.PhoneHasArrowKeys); + out.writeBoolean(false); + out.writeBoolean(Globals.UseAccelerometerAsArrowKeys); + out.writeBoolean(Globals.UseTouchscreenKeyboard); + out.writeInt(Globals.TouchscreenKeyboardSize); + out.writeInt(Globals.AccelerometerSensitivity); + out.writeInt(Globals.AccelerometerCenterPos); + out.writeInt(0); + out.writeInt(Globals.AudioBufferConfig); + out.writeInt(Globals.TouchscreenKeyboardTheme); + out.writeInt(Globals.RightClickMethod); + out.writeInt(Globals.ShowScreenUnderFinger); + out.writeInt(Globals.LeftClickMethod); + out.writeBoolean(Globals.MoveMouseWithJoystick); + out.writeBoolean(Globals.ClickMouseWithDpad); + out.writeInt(Globals.ClickScreenPressure); + out.writeInt(Globals.ClickScreenTouchspotSize); + out.writeBoolean(Globals.KeepAspectRatio); + out.writeInt(Globals.MoveMouseWithJoystickSpeed); + out.writeInt(Globals.MoveMouseWithJoystickAccel); + out.writeInt(SDL_Keys.JAVA_KEYCODE_LAST); + for (int i = 0; i < SDL_Keys.JAVA_KEYCODE_LAST; i++) { + out.writeInt(Globals.RemapHwKeycode[i]); + } + out.writeInt(Globals.RemapScreenKbKeycode.length); + for (int i = 0; i < Globals.RemapScreenKbKeycode.length; i++) { + out.writeInt(Globals.RemapScreenKbKeycode[i]); + } + out.writeInt(Globals.ScreenKbControlsShown.length); + for (int i = 0; i < Globals.ScreenKbControlsShown.length; i++) { + out.writeBoolean(Globals.ScreenKbControlsShown[i]); + } + out.writeInt(Globals.TouchscreenKeyboardTransparency); + out.writeInt(Globals.RemapMultitouchGestureKeycode.length); + for (int i = 0; i < Globals.RemapMultitouchGestureKeycode.length; i++) { + out.writeInt(Globals.RemapMultitouchGestureKeycode[i]); + out.writeBoolean(Globals.MultitouchGesturesUsed[i]); + } + out.writeInt(Globals.MultitouchGestureSensitivity); + for (int i = 0; i < Globals.TouchscreenCalibration.length; i++) + out.writeInt(Globals.TouchscreenCalibration[i]); + out.writeInt(Globals.DataDir.length()); + for (int i = 0; i < Globals.DataDir.length(); i++) + out.writeChar(Globals.DataDir.charAt(i)); + out.writeInt(Globals.CommandLine.length()); + for (int i = 0; i < Globals.CommandLine.length(); i++) + out.writeChar(Globals.CommandLine.charAt(i)); + out.writeInt(Globals.ScreenKbControlsLayout.length); + for (int i = 0; i < Globals.ScreenKbControlsLayout.length; i++) + for (int ii = 0; ii < 4; ii++) + out.writeInt(Globals.ScreenKbControlsLayout[i][ii]); + out.writeInt(Globals.LeftClickKey); + out.writeInt(Globals.RightClickKey); + out.writeBoolean(Globals.VideoLinearFilter); + out.writeInt(Globals.LeftClickTimeout); + out.writeInt(Globals.RightClickTimeout); + out.writeBoolean(Globals.RelativeMouseMovement); + out.writeInt(Globals.RelativeMouseMovementSpeed); + out.writeInt(Globals.RelativeMouseMovementAccel); + out.writeBoolean(Globals.MultiThreadedVideo); - out.writeInt(Globals.OptionalDataDownload.length); - for (int i = 0; i < Globals.OptionalDataDownload.length; i++) - out.writeBoolean(Globals.OptionalDataDownload[i]); - out.writeBoolean(false); // Unused - out.writeInt(Globals.TouchscreenKeyboardDrawSize); - // Gyroscope calibration data, now unused - out.writeFloat(0.0f); - out.writeFloat(0.0f); - out.writeFloat(0.0f); - out.writeFloat(0.0f); - out.writeFloat(0.0f); - out.writeFloat(0.0f); - out.writeFloat(0.0f); - out.writeFloat(0.0f); - out.writeFloat(0.0f); + out.writeInt(Globals.OptionalDataDownload.length); + for (int i = 0; i < Globals.OptionalDataDownload.length; i++) + out.writeBoolean(Globals.OptionalDataDownload[i]); + out.writeBoolean(false); // Unused + out.writeInt(Globals.TouchscreenKeyboardDrawSize); + // Gyroscope calibration data, now unused + out.writeFloat(0.0f); + out.writeFloat(0.0f); + out.writeFloat(0.0f); + out.writeFloat(0.0f); + out.writeFloat(0.0f); + out.writeFloat(0.0f); + out.writeFloat(0.0f); + out.writeFloat(0.0f); + out.writeFloat(0.0f); - out.writeBoolean(Globals.OuyaEmulation); - out.writeBoolean(Globals.HoverJitterFilter); - out.writeBoolean(Globals.MoveMouseWithGyroscope); - out.writeInt(Globals.MoveMouseWithGyroscopeSpeed); - out.writeBoolean(Globals.FingerHover); - out.writeBoolean(Globals.FloatingScreenJoystick); - out.writeBoolean(Globals.GenerateSubframeTouchEvents); - out.writeInt(Globals.VideoDepthBpp); - out.writeBoolean(Globals.HorizontalOrientation); - out.writeBoolean(Globals.ImmersiveMode); - out.writeBoolean(Globals.AutoDetectOrientation); - out.writeBoolean(Globals.TvBorders); - out.writeBoolean(Globals.ForceHardwareMouse); + out.writeBoolean(Globals.OuyaEmulation); + out.writeBoolean(Globals.HoverJitterFilter); + out.writeBoolean(Globals.MoveMouseWithGyroscope); + out.writeInt(Globals.MoveMouseWithGyroscopeSpeed); + out.writeBoolean(Globals.FingerHover); + out.writeBoolean(Globals.FloatingScreenJoystick); + out.writeBoolean(Globals.GenerateSubframeTouchEvents); + out.writeInt(Globals.VideoDepthBpp); + out.writeBoolean(Globals.HorizontalOrientation); + out.writeBoolean(Globals.ImmersiveMode); + out.writeBoolean(Globals.AutoDetectOrientation); + out.writeBoolean(Globals.TvBorders); + out.writeBoolean(Globals.ForceHardwareMouse); - out.close(); - settingsLoaded = true; + out.close(); + settingsLoaded = true; - } catch (FileNotFoundException e) { - } catch (SecurityException e) { - } catch (IOException e) { - } - ; + } catch (FileNotFoundException e) { + } catch (SecurityException e) { + } catch (IOException e) { + } + ; + } + + static void Load(final NeoXorgViewClient p) { + if (settingsLoaded) // Prevent starting twice + { + return; + } + Log.i("SDL", "libSDL: Settings.Load(): enter"); + nativeInitKeymap(); + for (int i = 0; i < SDL_Keys.JAVA_KEYCODE_LAST; i++) { + int sdlKey = nativeGetKeymapKey(i); + int idx = 0; + for (int ii = 0; ii < SDL_Keys.values.length; ii++) + if (SDL_Keys.values[ii] == sdlKey) + idx = ii; + Globals.RemapHwKeycode[i] = idx; + } + for (int i = 0; i < Globals.RemapScreenKbKeycode.length; i++) { + int sdlKey = nativeGetKeymapKeyScreenKb(i); + int idx = 0; + for (int ii = 0; ii < SDL_Keys.values.length; ii++) + if (SDL_Keys.values[ii] == sdlKey) + idx = ii; + Globals.RemapScreenKbKeycode[i] = idx; + } + Globals.ScreenKbControlsShown[0] = (Globals.AppNeedsArrowKeys || Globals.AppUsesJoystick); + Globals.ScreenKbControlsShown[1] = Globals.AppNeedsTextInput; + for (int i = 2; i < Globals.ScreenKbControlsShown.length; i++) + Globals.ScreenKbControlsShown[i] = (i - 2 < Globals.AppTouchscreenKeyboardKeysAmount); + if (Globals.AppUsesSecondJoystick) + Globals.ScreenKbControlsShown[8] = true; + if (Globals.AppUsesThirdJoystick) + Globals.ScreenKbControlsShown[9] = true; + for (int i = 0; i < Globals.RemapMultitouchGestureKeycode.length; i++) { + int sdlKey = nativeGetKeymapKeyMultitouchGesture(i); + int idx = 0; + for (int ii = 0; ii < SDL_Keys.values.length; ii++) + if (SDL_Keys.values[ii] == sdlKey) + idx = ii; + Globals.RemapMultitouchGestureKeycode[i] = idx; + } + for (int i = 0; i < Globals.MultitouchGesturesUsed.length; i++) + Globals.MultitouchGesturesUsed[i] = true; + // Adjust coordinates of on-screen buttons from 800x480 + int displayX = 800; + int displayY = 480; + try { + DisplayMetrics dm = new DisplayMetrics(); + p.getWindowManager().getDefaultDisplay().getMetrics(dm); + displayX = dm.widthPixels; + displayY = dm.heightPixels; + } catch (Exception eeeee) { + } + for (int i = 0; i < Globals.ScreenKbControlsLayout.length; i++) { + Globals.ScreenKbControlsLayout[i][0] *= (float) displayX / 800.0f; + Globals.ScreenKbControlsLayout[i][2] *= (float) displayX / 800.0f; + Globals.ScreenKbControlsLayout[i][1] *= (float) displayY / 480.0f; + Globals.ScreenKbControlsLayout[i][3] *= (float) displayY / 480.0f; + // Make them square + int wh = Math.min(Globals.ScreenKbControlsLayout[i][2] - Globals.ScreenKbControlsLayout[i][0], Globals.ScreenKbControlsLayout[i][3] - Globals.ScreenKbControlsLayout[i][1]); + Globals.ScreenKbControlsLayout[i][2] = Globals.ScreenKbControlsLayout[i][0] + wh; + Globals.ScreenKbControlsLayout[i][3] = Globals.ScreenKbControlsLayout[i][1] + wh; } - static void Load(final NeoXorgViewClient p) { - if (settingsLoaded) // Prevent starting twice - { - return; - } - Log.i("SDL", "libSDL: Settings.Load(): enter"); - nativeInitKeymap(); - for (int i = 0; i < SDL_Keys.JAVA_KEYCODE_LAST; i++) { - int sdlKey = nativeGetKeymapKey(i); - int idx = 0; - for (int ii = 0; ii < SDL_Keys.values.length; ii++) - if (SDL_Keys.values[ii] == sdlKey) - idx = ii; - Globals.RemapHwKeycode[i] = idx; - } - for (int i = 0; i < Globals.RemapScreenKbKeycode.length; i++) { - int sdlKey = nativeGetKeymapKeyScreenKb(i); - int idx = 0; - for (int ii = 0; ii < SDL_Keys.values.length; ii++) - if (SDL_Keys.values[ii] == sdlKey) - idx = ii; - Globals.RemapScreenKbKeycode[i] = idx; - } - Globals.ScreenKbControlsShown[0] = (Globals.AppNeedsArrowKeys || Globals.AppUsesJoystick); - Globals.ScreenKbControlsShown[1] = Globals.AppNeedsTextInput; - for (int i = 2; i < Globals.ScreenKbControlsShown.length; i++) - Globals.ScreenKbControlsShown[i] = (i - 2 < Globals.AppTouchscreenKeyboardKeysAmount); - if (Globals.AppUsesSecondJoystick) - Globals.ScreenKbControlsShown[8] = true; - if (Globals.AppUsesThirdJoystick) - Globals.ScreenKbControlsShown[9] = true; - for (int i = 0; i < Globals.RemapMultitouchGestureKeycode.length; i++) { - int sdlKey = nativeGetKeymapKeyMultitouchGesture(i); - int idx = 0; - for (int ii = 0; ii < SDL_Keys.values.length; ii++) - if (SDL_Keys.values[ii] == sdlKey) - idx = ii; - Globals.RemapMultitouchGestureKeycode[i] = idx; - } - for (int i = 0; i < Globals.MultitouchGesturesUsed.length; i++) - Globals.MultitouchGesturesUsed[i] = true; - // Adjust coordinates of on-screen buttons from 800x480 - int displayX = 800; - int displayY = 480; - try { - DisplayMetrics dm = new DisplayMetrics(); - p.getWindowManager().getDefaultDisplay().getMetrics(dm); - displayX = dm.widthPixels; - displayY = dm.heightPixels; - } catch (Exception eeeee) { - } - for (int i = 0; i < Globals.ScreenKbControlsLayout.length; i++) { - Globals.ScreenKbControlsLayout[i][0] *= (float) displayX / 800.0f; - Globals.ScreenKbControlsLayout[i][2] *= (float) displayX / 800.0f; - Globals.ScreenKbControlsLayout[i][1] *= (float) displayY / 480.0f; - Globals.ScreenKbControlsLayout[i][3] *= (float) displayY / 480.0f; - // Make them square - int wh = Math.min(Globals.ScreenKbControlsLayout[i][2] - Globals.ScreenKbControlsLayout[i][0], Globals.ScreenKbControlsLayout[i][3] - Globals.ScreenKbControlsLayout[i][1]); - Globals.ScreenKbControlsLayout[i][2] = Globals.ScreenKbControlsLayout[i][0] + wh; - Globals.ScreenKbControlsLayout[i][3] = Globals.ScreenKbControlsLayout[i][1] + wh; - } + Log.i("SDL", "android.os.Build.MODEL: " + Build.MODEL); + convertButtonSizeFromOldSdlVersion = false; - Log.i("SDL", "android.os.Build.MODEL: " + Build.MODEL); - convertButtonSizeFromOldSdlVersion = false; + try { + ObjectInputStream settingsFile = new ObjectInputStream(new FileInputStream(p.getContext().getFilesDir().getAbsolutePath() + "/" + SettingsFileName)); + if (settingsFile.readInt() != SETTINGS_FILE_VERSION) + throw new IOException(); + Globals.DownloadToSdcard = settingsFile.readBoolean(); + Globals.PhoneHasArrowKeys = settingsFile.readBoolean(); + settingsFile.readBoolean(); + Globals.UseAccelerometerAsArrowKeys = settingsFile.readBoolean(); + Globals.UseTouchscreenKeyboard = settingsFile.readBoolean(); + Globals.TouchscreenKeyboardSize = settingsFile.readInt(); + convertButtonSizeFromOldSdlVersion = true; // Will be changed to false if we read the remainder of the config file + Globals.AccelerometerSensitivity = settingsFile.readInt(); + Globals.AccelerometerCenterPos = settingsFile.readInt(); + settingsFile.readInt(); + Globals.AudioBufferConfig = settingsFile.readInt(); + Globals.TouchscreenKeyboardTheme = settingsFile.readInt(); + Globals.RightClickMethod = settingsFile.readInt(); + Globals.ShowScreenUnderFinger = settingsFile.readInt(); + Globals.LeftClickMethod = settingsFile.readInt(); + Globals.MoveMouseWithJoystick = settingsFile.readBoolean(); + Globals.ClickMouseWithDpad = settingsFile.readBoolean(); + Globals.ClickScreenPressure = settingsFile.readInt(); + Globals.ClickScreenTouchspotSize = settingsFile.readInt(); + Globals.KeepAspectRatio = settingsFile.readBoolean(); + Globals.MoveMouseWithJoystickSpeed = settingsFile.readInt(); + Globals.MoveMouseWithJoystickAccel = settingsFile.readInt(); + int readKeys = settingsFile.readInt(); + for (int i = 0; i < readKeys; i++) { + Globals.RemapHwKeycode[i] = settingsFile.readInt(); + } + if (settingsFile.readInt() != Globals.RemapScreenKbKeycode.length) + throw new IOException(); + for (int i = 0; i < Globals.RemapScreenKbKeycode.length; i++) { + Globals.RemapScreenKbKeycode[i] = settingsFile.readInt(); + } + if (settingsFile.readInt() != Globals.ScreenKbControlsShown.length) + throw new IOException(); + for (int i = 0; i < Globals.ScreenKbControlsShown.length; i++) { + Globals.ScreenKbControlsShown[i] = settingsFile.readBoolean(); + } + Globals.TouchscreenKeyboardTransparency = settingsFile.readInt(); + if (settingsFile.readInt() != Globals.RemapMultitouchGestureKeycode.length) + throw new IOException(); + for (int i = 0; i < Globals.RemapMultitouchGestureKeycode.length; i++) { + Globals.RemapMultitouchGestureKeycode[i] = settingsFile.readInt(); + Globals.MultitouchGesturesUsed[i] = settingsFile.readBoolean(); + } + Globals.MultitouchGestureSensitivity = settingsFile.readInt(); + for (int i = 0; i < Globals.TouchscreenCalibration.length; i++) + Globals.TouchscreenCalibration[i] = settingsFile.readInt(); + StringBuilder b = new StringBuilder(); + int len = settingsFile.readInt(); + for (int i = 0; i < len; i++) + b.append(settingsFile.readChar()); + Globals.DataDir = b.toString(); - try { - ObjectInputStream settingsFile = new ObjectInputStream(new FileInputStream(p.getContext().getFilesDir().getAbsolutePath() + "/" + SettingsFileName)); - if (settingsFile.readInt() != SETTINGS_FILE_VERSION) - throw new IOException(); - Globals.DownloadToSdcard = settingsFile.readBoolean(); - Globals.PhoneHasArrowKeys = settingsFile.readBoolean(); - settingsFile.readBoolean(); - Globals.UseAccelerometerAsArrowKeys = settingsFile.readBoolean(); - Globals.UseTouchscreenKeyboard = settingsFile.readBoolean(); - Globals.TouchscreenKeyboardSize = settingsFile.readInt(); - convertButtonSizeFromOldSdlVersion = true; // Will be changed to false if we read the remainder of the config file - Globals.AccelerometerSensitivity = settingsFile.readInt(); - Globals.AccelerometerCenterPos = settingsFile.readInt(); - settingsFile.readInt(); - Globals.AudioBufferConfig = settingsFile.readInt(); - Globals.TouchscreenKeyboardTheme = settingsFile.readInt(); - Globals.RightClickMethod = settingsFile.readInt(); - Globals.ShowScreenUnderFinger = settingsFile.readInt(); - Globals.LeftClickMethod = settingsFile.readInt(); - Globals.MoveMouseWithJoystick = settingsFile.readBoolean(); - Globals.ClickMouseWithDpad = settingsFile.readBoolean(); - Globals.ClickScreenPressure = settingsFile.readInt(); - Globals.ClickScreenTouchspotSize = settingsFile.readInt(); - Globals.KeepAspectRatio = settingsFile.readBoolean(); - Globals.MoveMouseWithJoystickSpeed = settingsFile.readInt(); - Globals.MoveMouseWithJoystickAccel = settingsFile.readInt(); - int readKeys = settingsFile.readInt(); - for (int i = 0; i < readKeys; i++) { - Globals.RemapHwKeycode[i] = settingsFile.readInt(); - } - if (settingsFile.readInt() != Globals.RemapScreenKbKeycode.length) - throw new IOException(); - for (int i = 0; i < Globals.RemapScreenKbKeycode.length; i++) { - Globals.RemapScreenKbKeycode[i] = settingsFile.readInt(); - } - if (settingsFile.readInt() != Globals.ScreenKbControlsShown.length) - throw new IOException(); - for (int i = 0; i < Globals.ScreenKbControlsShown.length; i++) { - Globals.ScreenKbControlsShown[i] = settingsFile.readBoolean(); - } - Globals.TouchscreenKeyboardTransparency = settingsFile.readInt(); - if (settingsFile.readInt() != Globals.RemapMultitouchGestureKeycode.length) - throw new IOException(); - for (int i = 0; i < Globals.RemapMultitouchGestureKeycode.length; i++) { - Globals.RemapMultitouchGestureKeycode[i] = settingsFile.readInt(); - Globals.MultitouchGesturesUsed[i] = settingsFile.readBoolean(); - } - Globals.MultitouchGestureSensitivity = settingsFile.readInt(); - for (int i = 0; i < Globals.TouchscreenCalibration.length; i++) - Globals.TouchscreenCalibration[i] = settingsFile.readInt(); - StringBuilder b = new StringBuilder(); - int len = settingsFile.readInt(); - for (int i = 0; i < len; i++) - b.append(settingsFile.readChar()); - Globals.DataDir = b.toString(); + b = new StringBuilder(); + len = settingsFile.readInt(); + for (int i = 0; i < len; i++) + b.append(settingsFile.readChar()); + Globals.CommandLine = b.toString(); - b = new StringBuilder(); - len = settingsFile.readInt(); - for (int i = 0; i < len; i++) - b.append(settingsFile.readChar()); - Globals.CommandLine = b.toString(); + if (settingsFile.readInt() != Globals.ScreenKbControlsLayout.length) + throw new IOException(); + for (int i = 0; i < Globals.ScreenKbControlsLayout.length; i++) + for (int ii = 0; ii < 4; ii++) + Globals.ScreenKbControlsLayout[i][ii] = settingsFile.readInt(); + Globals.LeftClickKey = settingsFile.readInt(); + Globals.RightClickKey = settingsFile.readInt(); + Globals.VideoLinearFilter = settingsFile.readBoolean(); + Globals.LeftClickTimeout = settingsFile.readInt(); + Globals.RightClickTimeout = settingsFile.readInt(); + Globals.RelativeMouseMovement = settingsFile.readBoolean(); + Globals.RelativeMouseMovementSpeed = settingsFile.readInt(); + Globals.RelativeMouseMovementAccel = settingsFile.readInt(); + Globals.MultiThreadedVideo = settingsFile.readBoolean(); - if (settingsFile.readInt() != Globals.ScreenKbControlsLayout.length) - throw new IOException(); - for (int i = 0; i < Globals.ScreenKbControlsLayout.length; i++) - for (int ii = 0; ii < 4; ii++) - Globals.ScreenKbControlsLayout[i][ii] = settingsFile.readInt(); - Globals.LeftClickKey = settingsFile.readInt(); - Globals.RightClickKey = settingsFile.readInt(); - Globals.VideoLinearFilter = settingsFile.readBoolean(); - Globals.LeftClickTimeout = settingsFile.readInt(); - Globals.RightClickTimeout = settingsFile.readInt(); - Globals.RelativeMouseMovement = settingsFile.readBoolean(); - Globals.RelativeMouseMovementSpeed = settingsFile.readInt(); - Globals.RelativeMouseMovementAccel = settingsFile.readInt(); - Globals.MultiThreadedVideo = settingsFile.readBoolean(); + Globals.OptionalDataDownload = new boolean[settingsFile.readInt()]; + for (int i = 0; i < Globals.OptionalDataDownload.length; i++) + Globals.OptionalDataDownload[i] = settingsFile.readBoolean(); + settingsFile.readBoolean(); // Unused + Globals.TouchscreenKeyboardDrawSize = settingsFile.readInt(); + // Gyroscope calibration data, now unused + settingsFile.readFloat(); + settingsFile.readFloat(); + settingsFile.readFloat(); + settingsFile.readFloat(); + settingsFile.readFloat(); + settingsFile.readFloat(); + settingsFile.readFloat(); + settingsFile.readFloat(); + settingsFile.readFloat(); - Globals.OptionalDataDownload = new boolean[settingsFile.readInt()]; - for (int i = 0; i < Globals.OptionalDataDownload.length; i++) - Globals.OptionalDataDownload[i] = settingsFile.readBoolean(); - settingsFile.readBoolean(); // Unused - Globals.TouchscreenKeyboardDrawSize = settingsFile.readInt(); - // Gyroscope calibration data, now unused - settingsFile.readFloat(); - settingsFile.readFloat(); - settingsFile.readFloat(); - settingsFile.readFloat(); - settingsFile.readFloat(); - settingsFile.readFloat(); - settingsFile.readFloat(); - settingsFile.readFloat(); - settingsFile.readFloat(); + Globals.OuyaEmulation = settingsFile.readBoolean(); + Globals.HoverJitterFilter = settingsFile.readBoolean(); + Globals.MoveMouseWithGyroscope = settingsFile.readBoolean(); + Globals.MoveMouseWithGyroscopeSpeed = settingsFile.readInt(); + Globals.FingerHover = settingsFile.readBoolean(); + Globals.FloatingScreenJoystick = settingsFile.readBoolean(); + Globals.GenerateSubframeTouchEvents = settingsFile.readBoolean(); + Globals.VideoDepthBpp = settingsFile.readInt(); + Globals.HorizontalOrientation = settingsFile.readBoolean(); + Globals.ImmersiveMode = settingsFile.readBoolean(); + Globals.AutoDetectOrientation = settingsFile.readBoolean(); + Globals.TvBorders = settingsFile.readBoolean(); + Globals.ForceHardwareMouse = settingsFile.readBoolean(); - Globals.OuyaEmulation = settingsFile.readBoolean(); - Globals.HoverJitterFilter = settingsFile.readBoolean(); - Globals.MoveMouseWithGyroscope = settingsFile.readBoolean(); - Globals.MoveMouseWithGyroscopeSpeed = settingsFile.readInt(); - Globals.FingerHover = settingsFile.readBoolean(); - Globals.FloatingScreenJoystick = settingsFile.readBoolean(); - Globals.GenerateSubframeTouchEvents = settingsFile.readBoolean(); - Globals.VideoDepthBpp = settingsFile.readInt(); - Globals.HorizontalOrientation = settingsFile.readBoolean(); - Globals.ImmersiveMode = settingsFile.readBoolean(); - Globals.AutoDetectOrientation = settingsFile.readBoolean(); - Globals.TvBorders = settingsFile.readBoolean(); - Globals.ForceHardwareMouse = settingsFile.readBoolean(); + settingsLoaded = true; - settingsLoaded = true; + Log.i("SDL", "libSDL: Settings.Load(): loaded settings successfully"); + settingsFile.close(); - Log.i("SDL", "libSDL: Settings.Load(): loaded settings successfully"); - settingsFile.close(); + return; - return; + } catch (FileNotFoundException e) { + Log.i("SDL", "libSDL: settings file not found: " + e); + } catch (SecurityException e) { + Log.i("SDL", "libSDL: settings file cannot be opened: " + e); + } catch (IOException e) { + Log.i("SDL", "libSDL: settings file cannot be read: " + e); + DeleteFilesOnUpgrade(p); + if (convertButtonSizeFromOldSdlVersion && Globals.TouchscreenKeyboardSize + 1 < Globals.TOUCHSCREEN_KEYBOARD_CUSTOM) { + Globals.TouchscreenKeyboardSize++; // New default button size is bigger, but we are keeping old button size for existing installations + //if (Globals.AppTouchscreenKeyboardKeysAmount <= 4 && Globals.TouchscreenKeyboardSize + 1 < Globals.TOUCHSCREEN_KEYBOARD_CUSTOM) + // Globals.TouchscreenKeyboardSize ++; // If there are only 4 buttons they are even bigger + } + } + ; - } catch (FileNotFoundException e) { - Log.i("SDL", "libSDL: settings file not found: " + e); - } catch (SecurityException e) { - Log.i("SDL", "libSDL: settings file cannot be opened: " + e); - } catch (IOException e) { - Log.i("SDL", "libSDL: settings file cannot be read: " + e); - DeleteFilesOnUpgrade(p); - if (convertButtonSizeFromOldSdlVersion && Globals.TouchscreenKeyboardSize + 1 < Globals.TOUCHSCREEN_KEYBOARD_CUSTOM) { - Globals.TouchscreenKeyboardSize++; // New default button size is bigger, but we are keeping old button size for existing installations - //if (Globals.AppTouchscreenKeyboardKeysAmount <= 4 && Globals.TouchscreenKeyboardSize + 1 < Globals.TOUCHSCREEN_KEYBOARD_CUSTOM) - // Globals.TouchscreenKeyboardSize ++; // If there are only 4 buttons they are even bigger - } - } - ; - - if (Globals.DataDir.length() == 0) { - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - Log.i("SDL", "libSDL: SD card or external storage is not mounted (state " + Environment.getExternalStorageState() + "), switching to the internal storage."); - Globals.DownloadToSdcard = false; - } - Globals.DataDir = Globals.DownloadToSdcard ? - SdcardAppPath.get().bestPath(p.getContext()) : - p.getContext().getFilesDir().getAbsolutePath(); - } - - Log.i("SDL", "libSDL: Settings.Load(): loading settings failed, running config dialog"); - p.initScreenOrientation(); + if (Globals.DataDir.length() == 0) { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + Log.i("SDL", "libSDL: SD card or external storage is not mounted (state " + Environment.getExternalStorageState() + "), switching to the internal storage."); + Globals.DownloadToSdcard = false; + } + Globals.DataDir = Globals.DownloadToSdcard ? + SdcardAppPath.get().bestPath(p.getContext()) : + p.getContext().getFilesDir().getAbsolutePath(); } - // =============================================================================================== + Log.i("SDL", "libSDL: Settings.Load(): loading settings failed, running config dialog"); + p.initScreenOrientation(); + } - public static boolean deleteRecursively(File dir) { - boolean success = true; - if (dir.isDirectory()) { - String[] children = dir.list(); - for (int i = 0; i < children.length; i++) { - if (!deleteRecursively(new File(dir, children[i]))) - success = false; - } + // =============================================================================================== + + public static boolean deleteRecursively(File dir) { + boolean success = true; + if (dir.isDirectory()) { + String[] children = dir.list(); + for (int i = 0; i < children.length; i++) { + if (!deleteRecursively(new File(dir, children[i]))) + success = false; + } + } + if (!dir.delete()) + success = false; + return success; + } + + public static boolean deleteRecursivelyAndLog(File dir) { + boolean success = true; + Log.v("SDL", "Deleting old file: " + dir.getAbsolutePath() + " exists " + dir.exists()); + if (dir.isDirectory()) { + String[] children = dir.list(); + for (int i = 0; i < children.length; i++) { + if (!deleteRecursively(new File(dir, children[i]))) + success = false; + } + } + if (!dir.delete()) + success = false; + return success; + } + + public static void DeleteFilesOnUpgrade(final NeoXorgViewClient p) { + String[] files = Globals.DeleteFilesOnUpgrade.split(" "); + for (String path : files) { + if (path.equals("")) + continue; + deleteRecursivelyAndLog(new File(p.getContext().getFilesDir().getAbsolutePath() + "/" + path)); + for (String sdpath : SdcardAppPath.get().allPaths(p.getContext())) + deleteRecursivelyAndLog(new File(sdpath + "/" + path)); + } + } + + public static void DeleteSdlConfigOnUpgradeAndRestart(final NeoXorgViewClient p) { + try { + ObjectOutputStream out = new ObjectOutputStream(p.getContext().openFileOutput(SettingsFileName, Context.MODE_PRIVATE)); + out.writeInt(-1); + out.close(); + } catch (FileNotFoundException e) { + } catch (IOException e) { + } + new File(p.getContext().getFilesDir() + "/" + SettingsFileName).delete(); + } + + // =============================================================================================== + + static void applyMouseEmulationOptions() { + if (Globals.AppUsesMouse) + nativeSetMouseUsed(Globals.RightClickMethod, + Globals.ShowScreenUnderFinger, + Globals.LeftClickMethod, + Globals.MoveMouseWithJoystick ? 1 : 0, + Globals.ClickMouseWithDpad ? 1 : 0, + Globals.ClickScreenPressure, + Globals.ClickScreenTouchspotSize, + Globals.MoveMouseWithJoystickSpeed, + Globals.MoveMouseWithJoystickAccel, + Globals.LeftClickKey, + Globals.RightClickKey, + Globals.LeftClickTimeout, + Globals.RightClickTimeout, + Globals.RelativeMouseMovement ? 1 : 0, + Globals.RelativeMouseMovementSpeed, + Globals.RelativeMouseMovementAccel, + Globals.ShowMouseCursor ? 1 : 0, + Globals.HoverJitterFilter ? 1 : 0, + Globals.RightMouseButtonLongPress ? 1 : 0, + Globals.MoveMouseWithGyroscope ? 1 : 0, + Globals.MoveMouseWithGyroscopeSpeed, + Globals.CompatibilityHacksForceScreenUpdateMouseClick ? 1 : 0, + Globals.ScreenFollowsMouse ? 1 : 0); + } + + static void Apply(NeoXorgViewClient p) { + setEnvVars(p); + nativeSetVideoDepth(Globals.VideoDepthBpp, Globals.NeedGles2 ? 1 : 0, Globals.NeedGles3 ? 1 : 0); + if (Globals.VideoLinearFilter) + nativeSetVideoLinearFilter(); + if (Globals.CompatibilityHacksVideo) { + Globals.MultiThreadedVideo = true; + Globals.SwVideoMode = true; + nativeSetCompatibilityHacks(); + } + if (Globals.SwVideoMode) + nativeSetVideoForceSoftwareMode(); + if (Globals.SwVideoMode && Globals.MultiThreadedVideo) + nativeSetVideoMultithreaded(); + applyMouseEmulationOptions(); + nativeSetJoystickUsed(Globals.AppUsesThirdJoystick ? 3 : (Globals.AppUsesSecondJoystick ? 2 : (Globals.AppUsesJoystick ? 1 : 0))); + if (Globals.AppUsesAccelerometer) + nativeSetAccelerometerUsed(); + if (Globals.AppUsesMultitouch) + nativeSetMultitouchUsed(); + nativeSetAccelerometerSettings(Globals.AccelerometerSensitivity, Globals.AccelerometerCenterPos); + if (Globals.UseTouchscreenKeyboard) { + boolean screenKbReallyUsed = false; + for (int i = 0; i < Globals.ScreenKbControlsShown.length; i++) + if (Globals.ScreenKbControlsShown[i]) + screenKbReallyUsed = true; + if (p.isRunningOnOUYA()) + screenKbReallyUsed = false; + if (screenKbReallyUsed) { + nativeSetTouchscreenKeyboardUsed(); + nativeSetupScreenKeyboard(Globals.TouchscreenKeyboardSize, + Globals.TouchscreenKeyboardDrawSize, + Globals.TouchscreenKeyboardTheme, + Globals.TouchscreenKeyboardTransparency, + Globals.FloatingScreenJoystick ? 1 : 0, + Globals.AppTouchscreenKeyboardKeysAmount); + SetupTouchscreenKeyboardGraphics(p.getContext()); + for (int i = 0; i < Globals.RemapScreenKbKeycode.length; i++) + nativeSetKeymapKeyScreenKb(i, SDL_Keys.values[Globals.RemapScreenKbKeycode[i]]); + if (Globals.TouchscreenKeyboardSize == Globals.TOUCHSCREEN_KEYBOARD_CUSTOM) { + for (int i = 0; i < Globals.ScreenKbControlsLayout.length; i++) + if (Globals.ScreenKbControlsLayout[i][0] < Globals.ScreenKbControlsLayout[i][2]) + nativeSetScreenKbKeyLayout(i, Globals.ScreenKbControlsLayout[i][0], Globals.ScreenKbControlsLayout[i][1], + Globals.ScreenKbControlsLayout[i][2], Globals.ScreenKbControlsLayout[i][3]); } - if (!dir.delete()) - success = false; - return success; + for (int i = 0; i < Globals.ScreenKbControlsShown.length; i++) + nativeSetScreenKbKeyUsed(i, Globals.ScreenKbControlsShown[i] ? 1 : 0); + } else + Globals.UseTouchscreenKeyboard = false; } - public static boolean deleteRecursivelyAndLog(File dir) { - boolean success = true; - Log.v("SDL", "Deleting old file: " + dir.getAbsolutePath() + " exists " + dir.exists()); - if (dir.isDirectory()) { - String[] children = dir.list(); - for (int i = 0; i < children.length; i++) { - if (!deleteRecursively(new File(dir, children[i]))) - success = false; - } - } - if (!dir.delete()) - success = false; - return success; + for (int i = 0; i < SDL_Keys.JAVA_KEYCODE_LAST; i++) + nativeSetKeymapKey(i, SDL_Keys.values[Globals.RemapHwKeycode[i]]); + for (int i = 0; i < Globals.RemapMultitouchGestureKeycode.length; i++) + nativeSetKeymapKeyMultitouchGesture(i, Globals.MultitouchGesturesUsed[i] ? SDL_Keys.values[Globals.RemapMultitouchGestureKeycode[i]] : 0); + nativeSetMultitouchGestureSensitivity(Globals.MultitouchGestureSensitivity); + if (Globals.TouchscreenCalibration[2] > Globals.TouchscreenCalibration[0]) + nativeSetTouchscreenCalibration(Globals.TouchscreenCalibration[0], Globals.TouchscreenCalibration[1], + Globals.TouchscreenCalibration[2], Globals.TouchscreenCalibration[3]); + } + + static void setEnvVars(NeoXorgViewClient p) { + String lang = Locale.getDefault().getLanguage(); + if (Locale.getDefault().getCountry().length() > 0) + lang = lang + "_" + Locale.getDefault().getCountry(); + Log.i("SDL", "libSDL: setting env LANGUAGE to '" + lang + "'"); + nativeSetEnv("LANG", lang); + nativeSetEnv("LANGUAGE", lang); + // TODO: get current user name and set envvar USER, the API is not availalbe on Android 1.6 so I don't bother with this + nativeSetEnv("APPDIR", p.getContext().getFilesDir().getAbsolutePath()); + nativeSetEnv("SECURE_STORAGE_DIR", p.getContext().getFilesDir().getAbsolutePath()); + nativeSetEnv("DATADIR", Globals.DataDir); + nativeSetEnv("UNSECURE_STORAGE_DIR", Globals.UnSecureDataDir); + SdcardAppPath.get().setEnv(p.getContext()); + nativeSetEnv("HOME", Globals.HomeDir); + nativeSetEnv("SDCARD", Environment.getExternalStorageDirectory().getAbsolutePath()); + nativeSetEnv("SDCARD_DOWNLOADS", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()); + nativeSetEnv("SDCARD_PICTURES", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath()); + nativeSetEnv("SDCARD_MOVIES", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath()); + nativeSetEnv("SDCARD_DCIM", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()); + nativeSetEnv("SDCARD_MUSIC", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath()); + nativeSetEnv("ANDROID_VERSION", String.valueOf(Build.VERSION.SDK_INT)); + nativeSetEnv("ANDROID_PACKAGE_NAME", p.getContext().getPackageName()); + nativeSetEnv("ANDROID_PACKAGE_PATH", p.getContext().getPackageCodePath()); + nativeSetEnv("ANDROID_MY_OWN_APP_FILE", p.getContext().getPackageResourcePath()); // This may be different from p.getPackageCodePath() on multi-user systems, but should still be the same .apk file + try { + nativeSetEnv("ANDROID_APP_NAME", p.getContext().getString(p.getContext().getApplicationInfo().labelRes)); + } catch (Exception eeeeee) { } - - public static void DeleteFilesOnUpgrade(final NeoXorgViewClient p) { - String[] files = Globals.DeleteFilesOnUpgrade.split(" "); - for (String path : files) { - if (path.equals("")) - continue; - deleteRecursivelyAndLog(new File(p.getContext().getFilesDir().getAbsolutePath() + "/" + path)); - for (String sdpath : SdcardAppPath.get().allPaths(p.getContext())) - deleteRecursivelyAndLog(new File(sdpath + "/" + path)); - } + Log.d("SDL", "libSDL: Is running on OUYA: " + p.isRunningOnOUYA()); + if (p.isRunningOnOUYA()) { + nativeSetEnv("OUYA", "1"); + nativeSetEnv("TV", "1"); + nativeSetEnv("ANDROID_TV", "1"); } - - public static void DeleteSdlConfigOnUpgradeAndRestart(final NeoXorgViewClient p) { - try { - ObjectOutputStream out = new ObjectOutputStream(p.getContext().openFileOutput(SettingsFileName, Context.MODE_PRIVATE)); - out.writeInt(-1); - out.close(); - } catch (FileNotFoundException e) { - } catch (IOException e) { - } - new File(p.getContext().getFilesDir() + "/" + SettingsFileName).delete(); - } - - // =============================================================================================== - - static void applyMouseEmulationOptions() { - if (Globals.AppUsesMouse) - nativeSetMouseUsed(Globals.RightClickMethod, - Globals.ShowScreenUnderFinger, - Globals.LeftClickMethod, - Globals.MoveMouseWithJoystick ? 1 : 0, - Globals.ClickMouseWithDpad ? 1 : 0, - Globals.ClickScreenPressure, - Globals.ClickScreenTouchspotSize, - Globals.MoveMouseWithJoystickSpeed, - Globals.MoveMouseWithJoystickAccel, - Globals.LeftClickKey, - Globals.RightClickKey, - Globals.LeftClickTimeout, - Globals.RightClickTimeout, - Globals.RelativeMouseMovement ? 1 : 0, - Globals.RelativeMouseMovementSpeed, - Globals.RelativeMouseMovementAccel, - Globals.ShowMouseCursor ? 1 : 0, - Globals.HoverJitterFilter ? 1 : 0, - Globals.RightMouseButtonLongPress ? 1 : 0, - Globals.MoveMouseWithGyroscope ? 1 : 0, - Globals.MoveMouseWithGyroscopeSpeed, - Globals.CompatibilityHacksForceScreenUpdateMouseClick ? 1 : 0, - Globals.ScreenFollowsMouse ? 1 : 0); - } - - static void Apply(NeoXorgViewClient p) { - setEnvVars(p); - nativeSetVideoDepth(Globals.VideoDepthBpp, Globals.NeedGles2 ? 1 : 0, Globals.NeedGles3 ? 1 : 0); - if (Globals.VideoLinearFilter) - nativeSetVideoLinearFilter(); - if (Globals.CompatibilityHacksVideo) { - Globals.MultiThreadedVideo = true; - Globals.SwVideoMode = true; - nativeSetCompatibilityHacks(); - } - if (Globals.SwVideoMode) - nativeSetVideoForceSoftwareMode(); - if (Globals.SwVideoMode && Globals.MultiThreadedVideo) - nativeSetVideoMultithreaded(); - applyMouseEmulationOptions(); - nativeSetJoystickUsed(Globals.AppUsesThirdJoystick ? 3 : (Globals.AppUsesSecondJoystick ? 2 : (Globals.AppUsesJoystick ? 1 : 0))); - if (Globals.AppUsesAccelerometer) - nativeSetAccelerometerUsed(); - if (Globals.AppUsesMultitouch) - nativeSetMultitouchUsed(); - nativeSetAccelerometerSettings(Globals.AccelerometerSensitivity, Globals.AccelerometerCenterPos); - if (Globals.UseTouchscreenKeyboard) { - boolean screenKbReallyUsed = false; - for (int i = 0; i < Globals.ScreenKbControlsShown.length; i++) - if (Globals.ScreenKbControlsShown[i]) - screenKbReallyUsed = true; - if (p.isRunningOnOUYA()) - screenKbReallyUsed = false; - if (screenKbReallyUsed) { - nativeSetTouchscreenKeyboardUsed(); - nativeSetupScreenKeyboard(Globals.TouchscreenKeyboardSize, - Globals.TouchscreenKeyboardDrawSize, - Globals.TouchscreenKeyboardTheme, - Globals.TouchscreenKeyboardTransparency, - Globals.FloatingScreenJoystick ? 1 : 0, - Globals.AppTouchscreenKeyboardKeysAmount); - SetupTouchscreenKeyboardGraphics(p.getContext()); - for (int i = 0; i < Globals.RemapScreenKbKeycode.length; i++) - nativeSetKeymapKeyScreenKb(i, SDL_Keys.values[Globals.RemapScreenKbKeycode[i]]); - if (Globals.TouchscreenKeyboardSize == Globals.TOUCHSCREEN_KEYBOARD_CUSTOM) { - for (int i = 0; i < Globals.ScreenKbControlsLayout.length; i++) - if (Globals.ScreenKbControlsLayout[i][0] < Globals.ScreenKbControlsLayout[i][2]) - nativeSetScreenKbKeyLayout(i, Globals.ScreenKbControlsLayout[i][0], Globals.ScreenKbControlsLayout[i][1], - Globals.ScreenKbControlsLayout[i][2], Globals.ScreenKbControlsLayout[i][3]); - } - for (int i = 0; i < Globals.ScreenKbControlsShown.length; i++) - nativeSetScreenKbKeyUsed(i, Globals.ScreenKbControlsShown[i] ? 1 : 0); - } else - Globals.UseTouchscreenKeyboard = false; - } - - for (int i = 0; i < SDL_Keys.JAVA_KEYCODE_LAST; i++) - nativeSetKeymapKey(i, SDL_Keys.values[Globals.RemapHwKeycode[i]]); - for (int i = 0; i < Globals.RemapMultitouchGestureKeycode.length; i++) - nativeSetKeymapKeyMultitouchGesture(i, Globals.MultitouchGesturesUsed[i] ? SDL_Keys.values[Globals.RemapMultitouchGestureKeycode[i]] : 0); - nativeSetMultitouchGestureSensitivity(Globals.MultitouchGestureSensitivity); - if (Globals.TouchscreenCalibration[2] > Globals.TouchscreenCalibration[0]) - nativeSetTouchscreenCalibration(Globals.TouchscreenCalibration[0], Globals.TouchscreenCalibration[1], - Globals.TouchscreenCalibration[2], Globals.TouchscreenCalibration[3]); - } - - static void setEnvVars(NeoXorgViewClient p) { - String lang = Locale.getDefault().getLanguage(); - if (Locale.getDefault().getCountry().length() > 0) - lang = lang + "_" + Locale.getDefault().getCountry(); - Log.i("SDL", "libSDL: setting env LANGUAGE to '" + lang + "'"); - nativeSetEnv("LANG", lang); - nativeSetEnv("LANGUAGE", lang); - // TODO: get current user name and set envvar USER, the API is not availalbe on Android 1.6 so I don't bother with this - nativeSetEnv("APPDIR", p.getContext().getFilesDir().getAbsolutePath()); - nativeSetEnv("SECURE_STORAGE_DIR", p.getContext().getFilesDir().getAbsolutePath()); - nativeSetEnv("DATADIR", Globals.DataDir); - nativeSetEnv("UNSECURE_STORAGE_DIR", Globals.UnSecureDataDir); - SdcardAppPath.get().setEnv(p.getContext()); - nativeSetEnv("HOME", Globals.HomeDir); - nativeSetEnv("SDCARD", Environment.getExternalStorageDirectory().getAbsolutePath()); - nativeSetEnv("SDCARD_DOWNLOADS", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()); - nativeSetEnv("SDCARD_PICTURES", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath()); - nativeSetEnv("SDCARD_MOVIES", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath()); - nativeSetEnv("SDCARD_DCIM", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()); - nativeSetEnv("SDCARD_MUSIC", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath()); - nativeSetEnv("ANDROID_VERSION", String.valueOf(Build.VERSION.SDK_INT)); - nativeSetEnv("ANDROID_PACKAGE_NAME", p.getContext().getPackageName()); - nativeSetEnv("ANDROID_PACKAGE_PATH", p.getContext().getPackageCodePath()); - nativeSetEnv("ANDROID_MY_OWN_APP_FILE", p.getContext().getPackageResourcePath()); // This may be different from p.getPackageCodePath() on multi-user systems, but should still be the same .apk file - try { - nativeSetEnv("ANDROID_APP_NAME", p.getContext().getString(p.getContext().getApplicationInfo().labelRes)); - } catch (Exception eeeeee) { - } - Log.d("SDL", "libSDL: Is running on OUYA: " + p.isRunningOnOUYA()); - if (p.isRunningOnOUYA()) { - nativeSetEnv("OUYA", "1"); - nativeSetEnv("TV", "1"); - nativeSetEnv("ANDROID_TV", "1"); - } - // TODO: Implement this + // TODO: Implement this // if (p.getIntent().getCategories() != null && p.getIntent().getCategories().contains("com.google.intent.category.CARDBOARD")) { // nativeSetEnv( "CARDBOARD", "1" ); // nativeSetEnv( "VR", "1" ); @@ -554,276 +546,276 @@ public class Settings { // } // if (p.getIntent().getStringExtra(RestartMainActivity.SDL_RESTART_PARAMS) != null) // nativeSetEnv( RestartMainActivity.SDL_RESTART_PARAMS, p.getIntent().getStringExtra(RestartMainActivity.SDL_RESTART_PARAMS) ); - try { - DisplayMetrics dm = new DisplayMetrics(); - p.getWindowManager().getDefaultDisplay().getMetrics(dm); - float xx = dm.widthPixels / dm.xdpi; - float yy = dm.heightPixels / dm.ydpi; - float x = Math.max(xx, yy); - float y = Math.min(xx, yy); - float displayInches = (float) Math.sqrt(x * x + y * y); - nativeSetEnv("DISPLAY_SIZE", String.valueOf(displayInches)); - nativeSetEnv("DISPLAY_SIZE_MM", String.valueOf((int) (displayInches * 25.4f))); - nativeSetEnv("DISPLAY_WIDTH", String.valueOf(x)); - nativeSetEnv("DISPLAY_HEIGHT", String.valueOf(y)); - nativeSetEnv("DISPLAY_WIDTH_MM", String.valueOf((int) (x * 25.4f))); - nativeSetEnv("DISPLAY_HEIGHT_MM", String.valueOf((int) (y * 25.4f))); - nativeSetEnv("DISPLAY_RESOLUTION_WIDTH", String.valueOf(Math.max(dm.widthPixels, dm.heightPixels))); - nativeSetEnv("DISPLAY_RESOLUTION_HEIGHT", String.valueOf(Math.min(dm.widthPixels, dm.heightPixels))); - } catch (Exception eeeee) { + try { + DisplayMetrics dm = new DisplayMetrics(); + p.getWindowManager().getDefaultDisplay().getMetrics(dm); + float xx = dm.widthPixels / dm.xdpi; + float yy = dm.heightPixels / dm.ydpi; + float x = Math.max(xx, yy); + float y = Math.min(xx, yy); + float displayInches = (float) Math.sqrt(x * x + y * y); + nativeSetEnv("DISPLAY_SIZE", String.valueOf(displayInches)); + nativeSetEnv("DISPLAY_SIZE_MM", String.valueOf((int) (displayInches * 25.4f))); + nativeSetEnv("DISPLAY_WIDTH", String.valueOf(x)); + nativeSetEnv("DISPLAY_HEIGHT", String.valueOf(y)); + nativeSetEnv("DISPLAY_WIDTH_MM", String.valueOf((int) (x * 25.4f))); + nativeSetEnv("DISPLAY_HEIGHT_MM", String.valueOf((int) (y * 25.4f))); + nativeSetEnv("DISPLAY_RESOLUTION_WIDTH", String.valueOf(Math.max(dm.widthPixels, dm.heightPixels))); + nativeSetEnv("DISPLAY_RESOLUTION_HEIGHT", String.valueOf(Math.min(dm.widthPixels, dm.heightPixels))); + } catch (Exception eeeee) { + } + } + + static byte[] loadRaw(Context p, int res) { + byte[] buf = new byte[65536 * 2]; + byte[] a = new byte[1048576 * 5]; // We need 5Mb buffer for Keen theme, and this Java code is inefficient + int written = 0; + try { + InputStream is = new GZIPInputStream(p.getResources().openRawResource(res)); + int readed = 0; + while ((readed = is.read(buf)) >= 0) { + if (written + readed > a.length) { + byte[] b = new byte[written + readed]; + System.arraycopy(a, 0, b, 0, written); + a = b; } + System.arraycopy(buf, 0, a, written, readed); + written += readed; + } + } catch (Exception e) { + } + ; + byte[] b = new byte[written]; + System.arraycopy(a, 0, b, 0, written); + return b; + } + + static void SetupTouchscreenKeyboardGraphics(Context p) { + if (Globals.UseTouchscreenKeyboard) { + if (Globals.TouchscreenKeyboardTheme < 0) + Globals.TouchscreenKeyboardTheme = 0; + if (Globals.TouchscreenKeyboardTheme > 9) + Globals.TouchscreenKeyboardTheme = 9; + + if (Globals.TouchscreenKeyboardTheme == 0) + nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.ultimatedroid)); + if (Globals.TouchscreenKeyboardTheme == 1) + nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.simpletheme)); + if (Globals.TouchscreenKeyboardTheme == 2) + nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.sun)); + if (Globals.TouchscreenKeyboardTheme == 3) + nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.keen)); + if (Globals.TouchscreenKeyboardTheme == 4) + nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.retro)); + if (Globals.TouchscreenKeyboardTheme == 5) + nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.gba)); + if (Globals.TouchscreenKeyboardTheme == 6) + nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.psx)); + if (Globals.TouchscreenKeyboardTheme == 7) + nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.snes)); + if (Globals.TouchscreenKeyboardTheme == 8) + nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.dualshock)); + if (Globals.TouchscreenKeyboardTheme == 9) + nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.n64)); + } + } + + abstract static class SdcardAppPath { + public static SdcardAppPath get() { + return Kitkat.Holder.sInstance; } - static byte[] loadRaw(Context p, int res) { - byte[] buf = new byte[65536 * 2]; - byte[] a = new byte[1048576 * 5]; // We need 5Mb buffer for Keen theme, and this Java code is inefficient - int written = 0; - try { - InputStream is = new GZIPInputStream(p.getResources().openRawResource(res)); - int readed = 0; - while ((readed = is.read(buf)) >= 0) { - if (written + readed > a.length) { - byte[] b = new byte[written + readed]; - System.arraycopy(a, 0, b, 0, written); - a = b; - } - System.arraycopy(buf, 0, a, written, readed); - written += readed; - } - } catch (Exception e) { - } - ; - byte[] b = new byte[written]; - System.arraycopy(a, 0, b, 0, written); - return b; + public String path(final Context p) { + return get().path(p); } - static void SetupTouchscreenKeyboardGraphics(Context p) { - if (Globals.UseTouchscreenKeyboard) { - if (Globals.TouchscreenKeyboardTheme < 0) - Globals.TouchscreenKeyboardTheme = 0; - if (Globals.TouchscreenKeyboardTheme > 9) - Globals.TouchscreenKeyboardTheme = 9; - - if (Globals.TouchscreenKeyboardTheme == 0) - nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.ultimatedroid)); - if (Globals.TouchscreenKeyboardTheme == 1) - nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.simpletheme)); - if (Globals.TouchscreenKeyboardTheme == 2) - nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.sun)); - if (Globals.TouchscreenKeyboardTheme == 3) - nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.keen)); - if (Globals.TouchscreenKeyboardTheme == 4) - nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.retro)); - if (Globals.TouchscreenKeyboardTheme == 5) - nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.gba)); - if (Globals.TouchscreenKeyboardTheme == 6) - nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.psx)); - if (Globals.TouchscreenKeyboardTheme == 7) - nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.snes)); - if (Globals.TouchscreenKeyboardTheme == 8) - nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.dualshock)); - if (Globals.TouchscreenKeyboardTheme == 9) - nativeSetupScreenKeyboardButtons(loadRaw(p, R.raw.n64)); - } + public void setEnv(final Context p) { + get().setEnv(p); } - abstract static class SdcardAppPath { - public static SdcardAppPath get() { - return Kitkat.Holder.sInstance; - } - - public String path(final Context p) { - return get().path(p); - } - - public void setEnv(final Context p) { - get().setEnv(p); - } - - public String bestPath(final Context p) { - return get().bestPath(p); - } - - ; - - public String[] allPaths(final Context p) { - return get().allPaths(p); - } - - ; - - private static class Froyo extends SdcardAppPath { - @Override - public String path(final Context p) { - if (p.getExternalFilesDir(null) == null) { - if (Environment.getExternalStorageDirectory() == null) - return "/sdcard/Android/data/" + p.getPackageName() + "/files"; - return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + p.getPackageName() + "/files"; - } - return p.getExternalFilesDir(null).getAbsolutePath(); - } - - @Override - public void setEnv(final Context p) { - nativeSetEnv("UNSECURE_STORAGE_DIR_0", Globals.DataDir); - } - - @Override - public String bestPath(final Context p) { - return path(p); - } - - @Override - public String[] allPaths(final Context p) { - return new String[]{path(p)}; - } - } - - private static class Kitkat extends Froyo { - private static class Holder { - private static final Kitkat sInstance = new Kitkat(); - } - - @Override - public String bestPath(final Context p) { - File[] paths = p.getExternalFilesDirs(null); - String ret = path(p); - long maxSize = -1; - for (File path : paths) { - if (path == null) - continue; - long size = -1; - try { - StatFs stat = new StatFs(path.getPath()); - size = (long) stat.getAvailableBlocks() * stat.getBlockSize() / 1024 / 1024; - } catch (Exception ee) { - } // Can throw an exception if we cannot read from SD card - - try { - path.mkdirs(); - new FileOutputStream(new File(path, ".nomedia")).close(); - } catch (Exception e) { - size = -1; // Not writable - } - - if (size > maxSize) { - maxSize = size; - ret = path.getAbsolutePath(); - } - } - return ret; - } - - ; - - @Override - public void setEnv(final Context p) { - File[] paths = p.getExternalFilesDirs(null); - int index = 0; - for (File path : paths) { - if (path == null) - continue; - if (!path.exists()) - path.mkdirs(); - nativeSetEnv("UNSECURE_STORAGE_DIR_" + index, path.getAbsolutePath()); - index++; - } - } - - @Override - public String[] allPaths(final Context p) { - ArrayList ret = new ArrayList(); - for (File path : p.getExternalFilesDirs(null)) { - if (path == null) - continue; - try { - path.mkdirs(); - new FileOutputStream(new File(path, ".nomedia")).close(); - } catch (Exception e) { - continue; - } - ret.add(path.getAbsolutePath()); - } - return ret.toArray(new String[0]); - } - } + public String bestPath(final Context p) { + return get().bestPath(p); } - static final int SDL_ANDROID_CONFIG_VIDEO_DEPTH_BPP = 0; + ; - public static void setConfigOptionFromSDL(int option, int value) { - switch (option) { - case SDL_ANDROID_CONFIG_VIDEO_DEPTH_BPP: - Globals.VideoDepthBpp = value; - break; - default: - Log.e("SDL", "setConfigOptionFromSDL: cannot find option with ID " + option + ", value " + value); - break; - } - Save(MainActivity.instance); + public String[] allPaths(final Context p) { + return get().allPaths(p); } - private static native void nativeSetAccelerometerSettings(int sensitivity, int centerPos); + ; - private static native void nativeSetMouseUsed(int RightClickMethod, int ShowScreenUnderFinger, int LeftClickMethod, - int MoveMouseWithJoystick, int ClickMouseWithDpad, int MaxForce, int MaxRadius, - int MoveMouseWithJoystickSpeed, int MoveMouseWithJoystickAccel, - int leftClickKeycode, int rightClickKeycode, - int leftClickTimeout, int rightClickTimeout, - int relativeMovement, int relativeMovementSpeed, - int relativeMovementAccel, int showMouseCursor, - int HoverJitterFilter, int RightMouseButtonLongPress, - int MoveMouseWithGyroscope, int MoveMouseWithGyroscopeSpeed, - int ForceScreenUpdateMouseClick, int ScreenFollowsMouse); + private static class Froyo extends SdcardAppPath { + @Override + public String path(final Context p) { + if (p.getExternalFilesDir(null) == null) { + if (Environment.getExternalStorageDirectory() == null) + return "/sdcard/Android/data/" + p.getPackageName() + "/files"; + return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + p.getPackageName() + "/files"; + } + return p.getExternalFilesDir(null).getAbsolutePath(); + } - private static native void nativeSetJoystickUsed(int amount); + @Override + public void setEnv(final Context p) { + nativeSetEnv("UNSECURE_STORAGE_DIR_0", Globals.DataDir); + } - private static native void nativeSetAccelerometerUsed(); + @Override + public String bestPath(final Context p) { + return path(p); + } - private static native void nativeSetMultitouchUsed(); + @Override + public String[] allPaths(final Context p) { + return new String[]{path(p)}; + } + } - private static native void nativeSetTouchscreenKeyboardUsed(); + private static class Kitkat extends Froyo { + private static class Holder { + private static final Kitkat sInstance = new Kitkat(); + } - private static native void nativeSetVideoLinearFilter(); + @Override + public String bestPath(final Context p) { + File[] paths = p.getExternalFilesDirs(null); + String ret = path(p); + long maxSize = -1; + for (File path : paths) { + if (path == null) + continue; + long size = -1; + try { + StatFs stat = new StatFs(path.getPath()); + size = (long) stat.getAvailableBlocks() * stat.getBlockSize() / 1024 / 1024; + } catch (Exception ee) { + } // Can throw an exception if we cannot read from SD card - private static native void nativeSetVideoDepth(int bpp, int gles2, int gles3); + try { + path.mkdirs(); + new FileOutputStream(new File(path, ".nomedia")).close(); + } catch (Exception e) { + size = -1; // Not writable + } - private static native void nativeSetCompatibilityHacks(); + if (size > maxSize) { + maxSize = size; + ret = path.getAbsolutePath(); + } + } + return ret; + } - private static native void nativeSetVideoMultithreaded(); + ; - private static native void nativeSetVideoForceSoftwareMode(); + @Override + public void setEnv(final Context p) { + File[] paths = p.getExternalFilesDirs(null); + int index = 0; + for (File path : paths) { + if (path == null) + continue; + if (!path.exists()) + path.mkdirs(); + nativeSetEnv("UNSECURE_STORAGE_DIR_" + index, path.getAbsolutePath()); + index++; + } + } - private static native void nativeSetupScreenKeyboard(int size, int drawsize, int theme, int transparency, int floatingScreenJoystick, int buttonAmount); + @Override + public String[] allPaths(final Context p) { + ArrayList ret = new ArrayList(); + for (File path : p.getExternalFilesDirs(null)) { + if (path == null) + continue; + try { + path.mkdirs(); + new FileOutputStream(new File(path, ".nomedia")).close(); + } catch (Exception e) { + continue; + } + ret.add(path.getAbsolutePath()); + } + return ret.toArray(new String[0]); + } + } + } - private static native void nativeSetupScreenKeyboardButtons(byte[] img); + static final int SDL_ANDROID_CONFIG_VIDEO_DEPTH_BPP = 0; - private static native void nativeInitKeymap(); + public static void setConfigOptionFromSDL(int option, int value) { + switch (option) { + case SDL_ANDROID_CONFIG_VIDEO_DEPTH_BPP: + Globals.VideoDepthBpp = value; + break; + default: + Log.e("SDL", "setConfigOptionFromSDL: cannot find option with ID " + option + ", value " + value); + break; + } + Save(MainActivity.instance); + } - private static native int nativeGetKeymapKey(int key); + private static native void nativeSetAccelerometerSettings(int sensitivity, int centerPos); - private static native void nativeSetKeymapKey(int javakey, int key); + private static native void nativeSetMouseUsed(int RightClickMethod, int ShowScreenUnderFinger, int LeftClickMethod, + int MoveMouseWithJoystick, int ClickMouseWithDpad, int MaxForce, int MaxRadius, + int MoveMouseWithJoystickSpeed, int MoveMouseWithJoystickAccel, + int leftClickKeycode, int rightClickKeycode, + int leftClickTimeout, int rightClickTimeout, + int relativeMovement, int relativeMovementSpeed, + int relativeMovementAccel, int showMouseCursor, + int HoverJitterFilter, int RightMouseButtonLongPress, + int MoveMouseWithGyroscope, int MoveMouseWithGyroscopeSpeed, + int ForceScreenUpdateMouseClick, int ScreenFollowsMouse); - private static native int nativeGetKeymapKeyScreenKb(int keynum); + private static native void nativeSetJoystickUsed(int amount); - private static native void nativeSetKeymapKeyScreenKb(int keynum, int key); + private static native void nativeSetAccelerometerUsed(); - private static native void nativeSetScreenKbKeyUsed(int keynum, int used); + private static native void nativeSetMultitouchUsed(); - private static native void nativeSetScreenKbKeyLayout(int keynum, int x1, int y1, int x2, int y2); + private static native void nativeSetTouchscreenKeyboardUsed(); - private static native int nativeGetKeymapKeyMultitouchGesture(int keynum); + private static native void nativeSetVideoLinearFilter(); - private static native void nativeSetKeymapKeyMultitouchGesture(int keynum, int key); + private static native void nativeSetVideoDepth(int bpp, int gles2, int gles3); - private static native void nativeSetMultitouchGestureSensitivity(int sensitivity); + private static native void nativeSetCompatibilityHacks(); - private static native void nativeSetTouchscreenCalibration(int x1, int y1, int x2, int y2); + private static native void nativeSetVideoMultithreaded(); - public static native void nativeSetEnv(final String name, final String value); + private static native void nativeSetVideoForceSoftwareMode(); - public static native int nativeChmod(final String name, int mode); + private static native void nativeSetupScreenKeyboard(int size, int drawsize, int theme, int transparency, int floatingScreenJoystick, int buttonAmount); - public static native void nativeChdir(final String dir); + private static native void nativeSetupScreenKeyboardButtons(byte[] img); + + private static native void nativeInitKeymap(); + + private static native int nativeGetKeymapKey(int key); + + private static native void nativeSetKeymapKey(int javakey, int key); + + private static native int nativeGetKeymapKeyScreenKb(int keynum); + + private static native void nativeSetKeymapKeyScreenKb(int keynum, int key); + + private static native void nativeSetScreenKbKeyUsed(int keynum, int used); + + private static native void nativeSetScreenKbKeyLayout(int keynum, int x1, int y1, int x2, int y2); + + private static native int nativeGetKeymapKeyMultitouchGesture(int keynum); + + private static native void nativeSetKeymapKeyMultitouchGesture(int keynum, int key); + + private static native void nativeSetMultitouchGestureSensitivity(int sensitivity); + + private static native void nativeSetTouchscreenCalibration(int x1, int y1, int x2, int y2); + + public static native void nativeSetEnv(final String name, final String value); + + public static native int nativeChmod(final String name, int mode); + + public static native void nativeChdir(final String dir); } diff --git a/Xorg/src/main/java/io/neoterm/SettingsMenu.java b/Xorg/src/main/java/io/neoterm/SettingsMenu.java index f9804ea..f09fbfd 100644 --- a/Xorg/src/main/java/io/neoterm/SettingsMenu.java +++ b/Xorg/src/main/java/io/neoterm/SettingsMenu.java @@ -24,189 +24,160 @@ package io.neoterm; import android.content.DialogInterface; import androidx.appcompat.app.AlertDialog; +import io.neoterm.xorg.R; import java.util.ArrayList; -import io.neoterm.xorg.R; +class SettingsMenu { + public static abstract class Menu { + // Should be overridden by children + abstract void run(final MainActivity p); -class SettingsMenu -{ - public static abstract class Menu - { - // Should be overridden by children - abstract void run(final MainActivity p); - abstract String title(final MainActivity p); - boolean enabled() - { - return true; - } - // Should not be overridden - boolean enabledOrHidden() - { - for( Menu m: Globals.HiddenMenuOptions ) - { - if( m.getClass().getName().equals( this.getClass().getName() ) ) - return false; - } - return enabled(); - } - void showMenuOptionsList(final MainActivity p, final Menu[] list) - { - menuStack.add(this); - ArrayList items = new ArrayList (); - for( Menu m: list ) - { - if(m.enabledOrHidden()) - items.add(m.title(p)); - } - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(title(p)); - builder.setItems(items.toArray(new CharSequence[0]), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - int selected = 0; + abstract String title(final MainActivity p); - for( Menu m: list ) - { - if(m.enabledOrHidden()) - { - if( selected == item ) - { - m.run(p); - return; - } - selected ++; - } - } - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBackOuterMenu(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + boolean enabled() { + return true; + } - static ArrayList

menuStack = new ArrayList (); + // Should not be overridden + boolean enabledOrHidden() { + for (Menu m : Globals.HiddenMenuOptions) { + if (m.getClass().getName().equals(this.getClass().getName())) + return false; + } + return enabled(); + } - public static void showConfig(final MainActivity p, final boolean firstStart) - { - Settings.settingsChanged = true; - if( Globals.OptionalDataDownload == null ) - { - String downloads[] = Globals.DataDownloadUrl; - Globals.OptionalDataDownload = new boolean[downloads.length]; - boolean oldFormat = true; - for( int i = 0; i < downloads.length; i++ ) - { - if( downloads[i].indexOf("!") == 0 ) - { - Globals.OptionalDataDownload[i] = true; - oldFormat = false; - } - } - if( oldFormat ) - Globals.OptionalDataDownload[0] = true; - } + void showMenuOptionsList(final MainActivity p, final Menu[] list) { + menuStack.add(this); + ArrayList items = new ArrayList(); + for (Menu m : list) { + if (m.enabledOrHidden()) + items.add(m.title(p)); + } + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(title(p)); + builder.setItems(items.toArray(new CharSequence[0]), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); + int selected = 0; - if(!firstStart) - new MainMenu().run(p); - else - { - if( Globals.StartupMenuButtonTimeout > 0 ) // If we did not disable startup menu altogether - { - for( Menu m: Globals.FirstStartMenuOptions ) - { - boolean hidden = false; - for( Menu m1: Globals.HiddenMenuOptions ) - { - if( m1.getClass().getName().equals( m.getClass().getName() ) ) - hidden = true; - } - if( ! hidden ) - menuStack.add(0, m); - } - } - goBack(p); - } - } + for (Menu m : list) { + if (m.enabledOrHidden()) { + if (selected == item) { + m.run(p); + return; + } + selected++; + } + } + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBackOuterMenu(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - static void goBack(final MainActivity p) - { - if(menuStack.isEmpty()) - { - Settings.Save(p); - } - else - { - Menu c = menuStack.remove(menuStack.size() - 1); - c.run(p); - } - } + static ArrayList menuStack = new ArrayList(); - static void goBackOuterMenu(final MainActivity p) - { - if(!menuStack.isEmpty()) - menuStack.remove(menuStack.size() - 1); - goBack(p); - } - - static class OkButton extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.ok); - } - void run (final MainActivity p) - { - goBackOuterMenu(p); - } - } + public static void showConfig(final MainActivity p, final boolean firstStart) { + Settings.settingsChanged = true; + if (Globals.OptionalDataDownload == null) { + String downloads[] = Globals.DataDownloadUrl; + Globals.OptionalDataDownload = new boolean[downloads.length]; + boolean oldFormat = true; + for (int i = 0; i < downloads.length; i++) { + if (downloads[i].indexOf("!") == 0) { + Globals.OptionalDataDownload[i] = true; + oldFormat = false; + } + } + if (oldFormat) + Globals.OptionalDataDownload[0] = true; + } - static class DummyMenu extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.ok); - } - void run (final MainActivity p) - { - goBack(p); - } - } + if (!firstStart) + new MainMenu().run(p); + else { + if (Globals.StartupMenuButtonTimeout > 0) // If we did not disable startup menu altogether + { + for (Menu m : Globals.FirstStartMenuOptions) { + boolean hidden = false; + for (Menu m1 : Globals.HiddenMenuOptions) { + if (m1.getClass().getName().equals(m.getClass().getName())) + hidden = true; + } + if (!hidden) + menuStack.add(0, m); + } + } + goBack(p); + } + } - static class MainMenu extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.device_config); - } - void run (final MainActivity p) - { - Menu options[] = - { - new SettingsMenuMisc.DownloadConfig(), - new SettingsMenuMisc.OptionalDownloadConfig(false), - new SettingsMenuKeyboard.KeyboardConfigMainMenu(), - new SettingsMenuMouse.MouseConfigMainMenu(), - new SettingsMenuMisc.AudioConfig(), - new SettingsMenuKeyboard.RemapHwKeysConfig(), - new SettingsMenuKeyboard.ScreenGesturesConfig(), - new SettingsMenuMisc.VideoSettingsConfig(), - new SettingsMenuMisc.CommandlineConfig(), - new SettingsMenuMisc.ResetToDefaultsConfig(), - new OkButton(), - }; - showMenuOptionsList(p, options); - } - } + static void goBack(final MainActivity p) { + if (menuStack.isEmpty()) { + Settings.Save(p); + } else { + Menu c = menuStack.remove(menuStack.size() - 1); + c.run(p); + } + } + + static void goBackOuterMenu(final MainActivity p) { + if (!menuStack.isEmpty()) + menuStack.remove(menuStack.size() - 1); + goBack(p); + } + + static class OkButton extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.ok); + } + + void run(final MainActivity p) { + goBackOuterMenu(p); + } + } + + static class DummyMenu extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.ok); + } + + void run(final MainActivity p) { + goBack(p); + } + } + + static class MainMenu extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.device_config); + } + + void run(final MainActivity p) { + Menu options[] = + { + new SettingsMenuMisc.DownloadConfig(), + new SettingsMenuMisc.OptionalDownloadConfig(false), + new SettingsMenuKeyboard.KeyboardConfigMainMenu(), + new SettingsMenuMouse.MouseConfigMainMenu(), + new SettingsMenuMisc.AudioConfig(), + new SettingsMenuKeyboard.RemapHwKeysConfig(), + new SettingsMenuKeyboard.ScreenGesturesConfig(), + new SettingsMenuMisc.VideoSettingsConfig(), + new SettingsMenuMisc.CommandlineConfig(), + new SettingsMenuMisc.ResetToDefaultsConfig(), + new OkButton(), + }; + showMenuOptionsList(p, options); + } + } } diff --git a/Xorg/src/main/java/io/neoterm/SettingsMenuKeyboard.java b/Xorg/src/main/java/io/neoterm/SettingsMenuKeyboard.java index e6c1e5d..27f12a2 100644 --- a/Xorg/src/main/java/io/neoterm/SettingsMenuKeyboard.java +++ b/Xorg/src/main/java/io/neoterm/SettingsMenuKeyboard.java @@ -27,7 +27,6 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.RectF; -import androidx.appcompat.app.AlertDialog; import android.util.DisplayMetrics; import android.view.KeyEvent; import android.view.MotionEvent; @@ -35,849 +34,742 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; +import androidx.appcompat.app.AlertDialog; +import io.neoterm.xorg.R; import java.util.Arrays; -import io.neoterm.xorg.R; +class SettingsMenuKeyboard extends SettingsMenu { + static class KeyboardConfigMainMenu extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.controls_screenkb); + } -class SettingsMenuKeyboard extends SettingsMenu -{ - static class KeyboardConfigMainMenu extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.controls_screenkb); - } - boolean enabled() - { - return Globals.UseTouchscreenKeyboard; - } - void run (final MainActivity p) - { - Menu options[] = - { - new ScreenKeyboardThemeConfig(), - new ScreenKeyboardSizeConfig(), - new ScreenKeyboardDrawSizeConfig(), - new ScreenKeyboardTransparencyConfig(), - new RemapScreenKbConfig(), - new CustomizeScreenKbLayout(), - new ScreenKeyboardAdvanced(), - new OkButton(), - }; - showMenuOptionsList(p, options); - } - } - - static class ScreenKeyboardSizeConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.controls_screenkb_size); - } - void run (final MainActivity p) - { - final CharSequence[] items = { p.getResources().getString(R.string.controls_screenkb_large), - p.getResources().getString(R.string.controls_screenkb_medium), - p.getResources().getString(R.string.controls_screenkb_small), - p.getResources().getString(R.string.controls_screenkb_tiny), - p.getResources().getString(R.string.controls_screenkb_custom) }; + boolean enabled() { + return Globals.UseTouchscreenKeyboard; + } - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.controls_screenkb_size)); - builder.setSingleChoiceItems(items, Globals.TouchscreenKeyboardSize, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.TouchscreenKeyboardSize = item; - dialog.dismiss(); - if( Globals.TouchscreenKeyboardSize == Globals.TOUCHSCREEN_KEYBOARD_CUSTOM ) - new CustomizeScreenKbLayout().run(p); - else - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + void run(final MainActivity p) { + Menu options[] = + { + new ScreenKeyboardThemeConfig(), + new ScreenKeyboardSizeConfig(), + new ScreenKeyboardDrawSizeConfig(), + new ScreenKeyboardTransparencyConfig(), + new RemapScreenKbConfig(), + new CustomizeScreenKbLayout(), + new ScreenKeyboardAdvanced(), + new OkButton(), + }; + showMenuOptionsList(p, options); + } + } - static class ScreenKeyboardDrawSizeConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.controls_screenkb_drawsize); - } - void run (final MainActivity p) - { - final CharSequence[] items = { p.getResources().getString(R.string.controls_screenkb_large), - p.getResources().getString(R.string.controls_screenkb_medium), - p.getResources().getString(R.string.controls_screenkb_small), - p.getResources().getString(R.string.controls_screenkb_tiny) }; + static class ScreenKeyboardSizeConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.controls_screenkb_size); + } - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.controls_screenkb_drawsize)); - builder.setSingleChoiceItems(items, Globals.TouchscreenKeyboardDrawSize, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.TouchscreenKeyboardDrawSize = item; + void run(final MainActivity p) { + final CharSequence[] items = {p.getResources().getString(R.string.controls_screenkb_large), + p.getResources().getString(R.string.controls_screenkb_medium), + p.getResources().getString(R.string.controls_screenkb_small), + p.getResources().getString(R.string.controls_screenkb_tiny), + p.getResources().getString(R.string.controls_screenkb_custom)}; - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.controls_screenkb_size)); + builder.setSingleChoiceItems(items, Globals.TouchscreenKeyboardSize, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.TouchscreenKeyboardSize = item; + dialog.dismiss(); + if (Globals.TouchscreenKeyboardSize == Globals.TOUCHSCREEN_KEYBOARD_CUSTOM) + new CustomizeScreenKbLayout().run(p); + else + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - static class ScreenKeyboardThemeConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.controls_screenkb_theme); - } - void run (final MainActivity p) - { - final CharSequence[] items = { - p.getResources().getString(R.string.controls_screenkb_by, "Ultimate Droid", "Sean Stieber"), - p.getResources().getString(R.string.controls_screenkb_by, "Simple Theme", "Beholder"), - p.getResources().getString(R.string.controls_screenkb_by, "Sun", "Sirea"), - p.getResources().getString(R.string.controls_screenkb_by, "Keen", "Gerstrong"), - p.getResources().getString(R.string.controls_screenkb_by, "Retro", "Santiago Radeff"), - p.getResources().getString(R.string.controls_screenkb_by, "Gba", "from RetroArch"), - p.getResources().getString(R.string.controls_screenkb_by, "Psx", "from RetroArch"), - p.getResources().getString(R.string.controls_screenkb_by, "Snes", "from RetroArch"), - p.getResources().getString(R.string.controls_screenkb_by, "DualShock", "from RetroArch"), - p.getResources().getString(R.string.controls_screenkb_by, "N64", "from RetroArch") - }; + static class ScreenKeyboardDrawSizeConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.controls_screenkb_drawsize); + } - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.controls_screenkb_theme)); - builder.setSingleChoiceItems(items, Globals.TouchscreenKeyboardTheme, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.TouchscreenKeyboardTheme = item; + void run(final MainActivity p) { + final CharSequence[] items = {p.getResources().getString(R.string.controls_screenkb_large), + p.getResources().getString(R.string.controls_screenkb_medium), + p.getResources().getString(R.string.controls_screenkb_small), + p.getResources().getString(R.string.controls_screenkb_tiny)}; - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.controls_screenkb_drawsize)); + builder.setSingleChoiceItems(items, Globals.TouchscreenKeyboardDrawSize, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.TouchscreenKeyboardDrawSize = item; - static class ScreenKeyboardTransparencyConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.controls_screenkb_transparency); - } - void run (final MainActivity p) - { - final CharSequence[] items = { p.getResources().getString(R.string.controls_screenkb_trans_0), - p.getResources().getString(R.string.controls_screenkb_trans_1), - p.getResources().getString(R.string.controls_screenkb_trans_2), - p.getResources().getString(R.string.controls_screenkb_trans_3), - p.getResources().getString(R.string.controls_screenkb_trans_4) }; + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.controls_screenkb_transparency)); - builder.setSingleChoiceItems(items, Globals.TouchscreenKeyboardTransparency, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.TouchscreenKeyboardTransparency = item; + static class ScreenKeyboardThemeConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.controls_screenkb_theme); + } - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } - - static class RemapHwKeysConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.remap_hwkeys); - } - void run (final MainActivity p) - { - p.setText(p.getResources().getString(R.string.remap_hwkeys_press)); - p.getVideoLayout().setOnKeyListener(new KeyRemapTool(p)); - } + void run(final MainActivity p) { + final CharSequence[] items = { + p.getResources().getString(R.string.controls_screenkb_by, "Ultimate Droid", "Sean Stieber"), + p.getResources().getString(R.string.controls_screenkb_by, "Simple Theme", "Beholder"), + p.getResources().getString(R.string.controls_screenkb_by, "Sun", "Sirea"), + p.getResources().getString(R.string.controls_screenkb_by, "Keen", "Gerstrong"), + p.getResources().getString(R.string.controls_screenkb_by, "Retro", "Santiago Radeff"), + p.getResources().getString(R.string.controls_screenkb_by, "Gba", "from RetroArch"), + p.getResources().getString(R.string.controls_screenkb_by, "Psx", "from RetroArch"), + p.getResources().getString(R.string.controls_screenkb_by, "Snes", "from RetroArch"), + p.getResources().getString(R.string.controls_screenkb_by, "DualShock", "from RetroArch"), + p.getResources().getString(R.string.controls_screenkb_by, "N64", "from RetroArch") + }; - public static class KeyRemapTool implements View.OnKeyListener - { - MainActivity p; - public KeyRemapTool(MainActivity _p) - { - p = _p; - } - - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) - { - p.getVideoLayout().setOnKeyListener(null); - int keyIndex = keyCode; - if( keyIndex < 0 ) - keyIndex = 0; - if( keyIndex > SDL_Keys.JAVA_KEYCODE_LAST ) - keyIndex = 0; + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.controls_screenkb_theme)); + builder.setSingleChoiceItems(items, Globals.TouchscreenKeyboardTheme, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.TouchscreenKeyboardTheme = item; - final int KeyIndexFinal = keyIndex; - CharSequence[] items = { - SDL_Keys.names[Globals.RemapScreenKbKeycode[0]], - SDL_Keys.names[Globals.RemapScreenKbKeycode[1]], - SDL_Keys.names[Globals.RemapScreenKbKeycode[2]], - SDL_Keys.names[Globals.RemapScreenKbKeycode[3]], - SDL_Keys.names[Globals.RemapScreenKbKeycode[4]], - SDL_Keys.names[Globals.RemapScreenKbKeycode[5]], - p.getResources().getString(R.string.remap_hwkeys_select_more_keys), - }; - - for( int i = 0; i < Math.min(6, Globals.AppTouchscreenKeyboardKeysNames.length); i++ ) - items[i] = Globals.AppTouchscreenKeyboardKeysNames[i].replace("_", " "); + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.remap_hwkeys_select_simple); - builder.setItems(items, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - if( item >= 6 ) - ShowAllKeys(KeyIndexFinal); - else - { - Globals.RemapHwKeycode[KeyIndexFinal] = Globals.RemapScreenKbKeycode[item]; - goBack(p); - } - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - return true; - } - public void ShowAllKeys(final int KeyIndex) - { - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.remap_hwkeys_select); - builder.setSingleChoiceItems(SDL_Keys.namesSorted, SDL_Keys.namesSortedBackIdx[Globals.RemapHwKeycode[KeyIndex]], new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.RemapHwKeycode[KeyIndex] = SDL_Keys.namesSortedIdx[item]; + static class ScreenKeyboardTransparencyConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.controls_screenkb_transparency); + } - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } - } + void run(final MainActivity p) { + final CharSequence[] items = {p.getResources().getString(R.string.controls_screenkb_trans_0), + p.getResources().getString(R.string.controls_screenkb_trans_1), + p.getResources().getString(R.string.controls_screenkb_trans_2), + p.getResources().getString(R.string.controls_screenkb_trans_3), + p.getResources().getString(R.string.controls_screenkb_trans_4)}; - static class RemapScreenKbConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.remap_screenkb); - } - //boolean enabled() { return true; }; - void run (final MainActivity p) - { - CharSequence[] items = { - p.getResources().getString(R.string.remap_screenkb_joystick), - p.getResources().getString(R.string.remap_screenkb_button_text), - p.getResources().getString(R.string.remap_screenkb_button) + " 1", - p.getResources().getString(R.string.remap_screenkb_button) + " 2", - p.getResources().getString(R.string.remap_screenkb_button) + " 3", - p.getResources().getString(R.string.remap_screenkb_button) + " 4", - p.getResources().getString(R.string.remap_screenkb_button) + " 5", - p.getResources().getString(R.string.remap_screenkb_button) + " 6", - }; + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.controls_screenkb_transparency)); + builder.setSingleChoiceItems(items, Globals.TouchscreenKeyboardTransparency, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.TouchscreenKeyboardTransparency = item; - boolean defaults[] = Arrays.copyOf(Globals.ScreenKbControlsShown, Globals.ScreenKbControlsShown.length); - if( Globals.AppUsesSecondJoystick ) - { - items = Arrays.copyOf(items, items.length + 1); - items[items.length - 1] = p.getResources().getString(R.string.remap_screenkb_joystick) + " 2"; - defaults = Arrays.copyOf(defaults, defaults.length + 1); - defaults[defaults.length - 1] = true; - } - if( Globals.AppUsesThirdJoystick ) - { - items = Arrays.copyOf(items, items.length + 1); - items[items.length - 1] = p.getResources().getString(R.string.remap_screenkb_joystick) + " 3"; - defaults = Arrays.copyOf(defaults, defaults.length + 1); - defaults[defaults.length - 1] = true; - } + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - for( int i = 0; i < Math.min(6, Globals.AppTouchscreenKeyboardKeysNames.length); i++ ) - items[i+2] = items[i+2] + " - " + Globals.AppTouchscreenKeyboardKeysNames[i].replace("_", " "); + static class RemapHwKeysConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.remap_hwkeys); + } - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.remap_screenkb)); - builder.setMultiChoiceItems(items, defaults, new DialogInterface.OnMultiChoiceClickListener() - { - public void onClick(DialogInterface dialog, int item, boolean isChecked) - { - Globals.ScreenKbControlsShown[item] = isChecked; - } - }); - builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - showRemapScreenKbConfig2(p, 0); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } + void run(final MainActivity p) { + p.setText(p.getResources().getString(R.string.remap_hwkeys_press)); + p.getVideoLayout().setOnKeyListener(new KeyRemapTool(p)); + } - static void showRemapScreenKbConfig2(final MainActivity p, final int currentButton) - { - CharSequence[] items = { - p.getResources().getString(R.string.remap_screenkb_button) + " 1", - p.getResources().getString(R.string.remap_screenkb_button) + " 2", - p.getResources().getString(R.string.remap_screenkb_button) + " 3", - p.getResources().getString(R.string.remap_screenkb_button) + " 4", - p.getResources().getString(R.string.remap_screenkb_button) + " 5", - p.getResources().getString(R.string.remap_screenkb_button) + " 6", - }; + public static class KeyRemapTool implements View.OnKeyListener { + MainActivity p; - for( int i = 0; i < Math.min(6, Globals.AppTouchscreenKeyboardKeysNames.length); i++ ) - items[i] = items[i] + " - " + Globals.AppTouchscreenKeyboardKeysNames[i].replace("_", " "); + public KeyRemapTool(MainActivity _p) { + p = _p; + } - if( currentButton >= Globals.RemapScreenKbKeycode.length ) - { - goBack(p); - return; - } - if( ! Globals.ScreenKbControlsShown[currentButton + 2] ) - { - showRemapScreenKbConfig2(p, currentButton + 1); - return; - } + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + p.getVideoLayout().setOnKeyListener(null); + int keyIndex = keyCode; + if (keyIndex < 0) + keyIndex = 0; + if (keyIndex > SDL_Keys.JAVA_KEYCODE_LAST) + keyIndex = 0; - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(items[currentButton]); - builder.setSingleChoiceItems(SDL_Keys.namesSorted, SDL_Keys.namesSortedBackIdx[Globals.RemapScreenKbKeycode[currentButton]], new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.RemapScreenKbKeycode[currentButton] = SDL_Keys.namesSortedIdx[item]; + final int KeyIndexFinal = keyIndex; + CharSequence[] items = { + SDL_Keys.names[Globals.RemapScreenKbKeycode[0]], + SDL_Keys.names[Globals.RemapScreenKbKeycode[1]], + SDL_Keys.names[Globals.RemapScreenKbKeycode[2]], + SDL_Keys.names[Globals.RemapScreenKbKeycode[3]], + SDL_Keys.names[Globals.RemapScreenKbKeycode[4]], + SDL_Keys.names[Globals.RemapScreenKbKeycode[5]], + p.getResources().getString(R.string.remap_hwkeys_select_more_keys), + }; - dialog.dismiss(); - showRemapScreenKbConfig2(p, currentButton + 1); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } - - static class ScreenGesturesConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.remap_screenkb_button_gestures); - } - //boolean enabled() { return true; }; - void run (final MainActivity p) - { - CharSequence[] items = { - p.getResources().getString(R.string.remap_screenkb_button_zoomin), - p.getResources().getString(R.string.remap_screenkb_button_zoomout), - p.getResources().getString(R.string.remap_screenkb_button_rotateleft), - p.getResources().getString(R.string.remap_screenkb_button_rotateright), - }; + for (int i = 0; i < Math.min(6, Globals.AppTouchscreenKeyboardKeysNames.length); i++) + items[i] = Globals.AppTouchscreenKeyboardKeysNames[i].replace("_", " "); - boolean defaults[] = { - Globals.MultitouchGesturesUsed[0], - Globals.MultitouchGesturesUsed[1], - Globals.MultitouchGesturesUsed[2], - Globals.MultitouchGesturesUsed[3], - }; - - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.remap_screenkb_button_gestures)); - builder.setMultiChoiceItems(items, defaults, new DialogInterface.OnMultiChoiceClickListener() - { - public void onClick(DialogInterface dialog, int item, boolean isChecked) - { - Globals.MultitouchGesturesUsed[item] = isChecked; - } - }); - builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - showScreenGesturesConfig2(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.remap_hwkeys_select_simple); + builder.setItems(items, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); + if (item >= 6) + ShowAllKeys(KeyIndexFinal); + else { + Globals.RemapHwKeycode[KeyIndexFinal] = Globals.RemapScreenKbKeycode[item]; + goBack(p); + } + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + return true; + } - static void showScreenGesturesConfig2(final MainActivity p) - { - final CharSequence[] items = { - p.getResources().getString(R.string.accel_slow), - p.getResources().getString(R.string.accel_medium), - p.getResources().getString(R.string.accel_fast), - p.getResources().getString(R.string.accel_veryfast) - }; + public void ShowAllKeys(final int KeyIndex) { + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.remap_hwkeys_select); + builder.setSingleChoiceItems(SDL_Keys.namesSorted, SDL_Keys.namesSortedBackIdx[Globals.RemapHwKeycode[KeyIndex]], new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.RemapHwKeycode[KeyIndex] = SDL_Keys.namesSortedIdx[item]; - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.remap_screenkb_button_gestures_sensitivity); - builder.setSingleChoiceItems(items, Globals.MultitouchGestureSensitivity, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.MultitouchGestureSensitivity = item; + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } + } - dialog.dismiss(); - showScreenGesturesConfig3(p, 0); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } + static class RemapScreenKbConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.remap_screenkb); + } - static void showScreenGesturesConfig3(final MainActivity p, final int currentButton) - { - CharSequence[] items = { - p.getResources().getString(R.string.remap_screenkb_button_zoomin), - p.getResources().getString(R.string.remap_screenkb_button_zoomout), - p.getResources().getString(R.string.remap_screenkb_button_rotateleft), - p.getResources().getString(R.string.remap_screenkb_button_rotateright), - }; - - if( currentButton >= Globals.RemapMultitouchGestureKeycode.length ) - { - goBack(p); - return; - } - if( ! Globals.MultitouchGesturesUsed[currentButton] ) - { - showScreenGesturesConfig3(p, currentButton + 1); - return; - } + //boolean enabled() { return true; }; + void run(final MainActivity p) { + CharSequence[] items = { + p.getResources().getString(R.string.remap_screenkb_joystick), + p.getResources().getString(R.string.remap_screenkb_button_text), + p.getResources().getString(R.string.remap_screenkb_button) + " 1", + p.getResources().getString(R.string.remap_screenkb_button) + " 2", + p.getResources().getString(R.string.remap_screenkb_button) + " 3", + p.getResources().getString(R.string.remap_screenkb_button) + " 4", + p.getResources().getString(R.string.remap_screenkb_button) + " 5", + p.getResources().getString(R.string.remap_screenkb_button) + " 6", + }; - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(items[currentButton]); - builder.setSingleChoiceItems(SDL_Keys.namesSorted, SDL_Keys.namesSortedBackIdx[Globals.RemapMultitouchGestureKeycode[currentButton]], new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.RemapMultitouchGestureKeycode[currentButton] = SDL_Keys.namesSortedIdx[item]; + boolean defaults[] = Arrays.copyOf(Globals.ScreenKbControlsShown, Globals.ScreenKbControlsShown.length); + if (Globals.AppUsesSecondJoystick) { + items = Arrays.copyOf(items, items.length + 1); + items[items.length - 1] = p.getResources().getString(R.string.remap_screenkb_joystick) + " 2"; + defaults = Arrays.copyOf(defaults, defaults.length + 1); + defaults[defaults.length - 1] = true; + } + if (Globals.AppUsesThirdJoystick) { + items = Arrays.copyOf(items, items.length + 1); + items[items.length - 1] = p.getResources().getString(R.string.remap_screenkb_joystick) + " 3"; + defaults = Arrays.copyOf(defaults, defaults.length + 1); + defaults[defaults.length - 1] = true; + } - dialog.dismiss(); - showScreenGesturesConfig3(p, currentButton + 1); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + for (int i = 0; i < Math.min(6, Globals.AppTouchscreenKeyboardKeysNames.length); i++) + items[i + 2] = items[i + 2] + " - " + Globals.AppTouchscreenKeyboardKeysNames[i].replace("_", " "); - static class CustomizeScreenKbLayout extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.screenkb_custom_layout); - } - //boolean enabled() { return true; }; - void run (final MainActivity p) - { - p.setText(p.getResources().getString(R.string.screenkb_custom_layout_help)); - CustomizeScreenKbLayoutTool tool = new CustomizeScreenKbLayoutTool(p); - Globals.TouchscreenKeyboardSize = Globals.TOUCHSCREEN_KEYBOARD_CUSTOM; - } + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.remap_screenkb)); + builder.setMultiChoiceItems(items, defaults, new DialogInterface.OnMultiChoiceClickListener() { + public void onClick(DialogInterface dialog, int item, boolean isChecked) { + Globals.ScreenKbControlsShown[item] = isChecked; + } + }); + builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); + showRemapScreenKbConfig2(p, 0); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } - static class CustomizeScreenKbLayoutTool implements View.OnTouchListener, View.OnKeyListener - { - MainActivity p; - FrameLayout layout = null; - ImageView imgs[] = new ImageView[Globals.ScreenKbControlsLayout.length]; - Bitmap bmps[] = new Bitmap[Globals.ScreenKbControlsLayout.length]; - ImageView boundary = null; - Bitmap boundaryBmp = null; - int currentButton = 0; - int buttons[] = { - R.drawable.dpad, - R.drawable.keyboard, - R.drawable.b1, - R.drawable.b2, - R.drawable.b3, - R.drawable.b4, - R.drawable.b5, - R.drawable.b6, - R.drawable.dpad, - R.drawable.dpad - }; - int oldX = 0, oldY = 0; - boolean resizing = false; - - public CustomizeScreenKbLayoutTool(MainActivity _p) - { - p = _p; - layout = new FrameLayout(p); - p.getVideoLayout().addView(layout); - layout.setFocusable(true); - layout.setFocusableInTouchMode(true); - layout.requestFocus(); - layout.setOnTouchListener(this); - layout.setOnKeyListener(this); - boundary = new ImageView(p); - boundary.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - boundary.setScaleType(ImageView.ScaleType.MATRIX); - boundaryBmp = BitmapFactory.decodeResource( p.getResources(), R.drawable.rectangle ); - boundary.setImageBitmap(boundaryBmp); - layout.addView(boundary); - currentButton = -1; - if( Globals.TouchscreenKeyboardTheme == 2 ) - { - buttons = new int[] { - R.drawable.sun_dpad, - R.drawable.sun_keyboard, - R.drawable.sun_b1, - R.drawable.sun_b2, - R.drawable.sun_b3, - R.drawable.sun_b4, - R.drawable.sun_b5, - R.drawable.sun_b6, - R.drawable.sun_dpad, - R.drawable.sun_dpad - }; - } + static void showRemapScreenKbConfig2(final MainActivity p, final int currentButton) { + CharSequence[] items = { + p.getResources().getString(R.string.remap_screenkb_button) + " 1", + p.getResources().getString(R.string.remap_screenkb_button) + " 2", + p.getResources().getString(R.string.remap_screenkb_button) + " 3", + p.getResources().getString(R.string.remap_screenkb_button) + " 4", + p.getResources().getString(R.string.remap_screenkb_button) + " 5", + p.getResources().getString(R.string.remap_screenkb_button) + " 6", + }; - int displayX = 800; - int displayY = 480; - try { - DisplayMetrics dm = new DisplayMetrics(); - p.getWindowManager().getDefaultDisplay().getMetrics(dm); - displayX = dm.widthPixels; - displayY = dm.heightPixels; - } catch (Exception eeeee) {} + for (int i = 0; i < Math.min(6, Globals.AppTouchscreenKeyboardKeysNames.length); i++) + items[i] = items[i] + " - " + Globals.AppTouchscreenKeyboardKeysNames[i].replace("_", " "); - for( int i = 0; i < Globals.ScreenKbControlsLayout.length; i++ ) - { - if( ! Globals.ScreenKbControlsShown[i] ) - continue; - if( currentButton == -1 ) - currentButton = i; - //Log.i("SDL", "Screen kb button " + i + " coords " + Globals.ScreenKbControlsLayout[i][0] + ":" + Globals.ScreenKbControlsLayout[i][1] + ":" + Globals.ScreenKbControlsLayout[i][2] + ":" + Globals.ScreenKbControlsLayout[i][3] ); - // Check if the button is off screen edge or shrunk to zero - if( Globals.ScreenKbControlsLayout[i][0] > Globals.ScreenKbControlsLayout[i][2] - displayY/12 ) - Globals.ScreenKbControlsLayout[i][0] = Globals.ScreenKbControlsLayout[i][2] - displayY/12; - if( Globals.ScreenKbControlsLayout[i][1] > Globals.ScreenKbControlsLayout[i][3] - displayY/12 ) - Globals.ScreenKbControlsLayout[i][1] = Globals.ScreenKbControlsLayout[i][3] - displayY/12; - if( Globals.ScreenKbControlsLayout[i][0] < Globals.ScreenKbControlsLayout[i][2] - displayY*2/3 ) - Globals.ScreenKbControlsLayout[i][0] = Globals.ScreenKbControlsLayout[i][2] - displayY*2/3; - if( Globals.ScreenKbControlsLayout[i][1] < Globals.ScreenKbControlsLayout[i][3] - displayY*2/3 ) - Globals.ScreenKbControlsLayout[i][1] = Globals.ScreenKbControlsLayout[i][3] - displayY*2/3; - if( Globals.ScreenKbControlsLayout[i][0] < 0 ) - { - Globals.ScreenKbControlsLayout[i][2] += -Globals.ScreenKbControlsLayout[i][0]; - Globals.ScreenKbControlsLayout[i][0] = 0; - } - if( Globals.ScreenKbControlsLayout[i][2] > displayX ) - { - Globals.ScreenKbControlsLayout[i][0] -= Globals.ScreenKbControlsLayout[i][2] - displayX; - Globals.ScreenKbControlsLayout[i][2] = displayX; - } - if( Globals.ScreenKbControlsLayout[i][1] < 0 ) - { - Globals.ScreenKbControlsLayout[i][3] += -Globals.ScreenKbControlsLayout[i][1]; - Globals.ScreenKbControlsLayout[i][1] = 0; - } - if( Globals.ScreenKbControlsLayout[i][3] > displayY ) - { - Globals.ScreenKbControlsLayout[i][1] -= Globals.ScreenKbControlsLayout[i][3] - displayY; - Globals.ScreenKbControlsLayout[i][3] = displayY; - } - //Log.i("SDL", "After bounds check coords " + Globals.ScreenKbControlsLayout[i][0] + ":" + Globals.ScreenKbControlsLayout[i][1] + ":" + Globals.ScreenKbControlsLayout[i][2] + ":" + Globals.ScreenKbControlsLayout[i][3] ); + if (currentButton >= Globals.RemapScreenKbKeycode.length) { + goBack(p); + return; + } + if (!Globals.ScreenKbControlsShown[currentButton + 2]) { + showRemapScreenKbConfig2(p, currentButton + 1); + return; + } - imgs[i] = new ImageView(p); - imgs[i].setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - imgs[i].setScaleType(ImageView.ScaleType.MATRIX); - bmps[i] = BitmapFactory.decodeResource( p.getResources(), buttons[i] ); - imgs[i].setImageBitmap(bmps[i]); - imgs[i].setAlpha(128); - layout.addView(imgs[i]); - Matrix m = new Matrix(); - RectF src = new RectF(0, 0, bmps[i].getWidth(), bmps[i].getHeight()); - RectF dst = new RectF(Globals.ScreenKbControlsLayout[i][0], Globals.ScreenKbControlsLayout[i][1], - Globals.ScreenKbControlsLayout[i][2], Globals.ScreenKbControlsLayout[i][3]); - m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); - imgs[i].setImageMatrix(m); - } - boundary.bringToFront(); - if( currentButton == -1 ) - onKey( null, KeyEvent.KEYCODE_BACK, null ); // All buttons disabled - do not show anything - else - setupButton(currentButton); - } - - void setupButton(int i) - { - Matrix m = new Matrix(); - RectF src = new RectF(0, 0, bmps[i].getWidth(), bmps[i].getHeight()); - RectF dst = new RectF(Globals.ScreenKbControlsLayout[i][0], Globals.ScreenKbControlsLayout[i][1], - Globals.ScreenKbControlsLayout[i][2], Globals.ScreenKbControlsLayout[i][3]); - m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); - imgs[i].setImageMatrix(m); - m = new Matrix(); - src = new RectF(0, 0, boundaryBmp.getWidth(), boundaryBmp.getHeight()); - m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); - boundary.setImageMatrix(m); - String buttonText = ""; - if( i >= 2 && i <= 7 ) - buttonText = p.getResources().getString(R.string.remap_screenkb_button) + (i - 2); - if( i >= 2 && i - 2 < Globals.AppTouchscreenKeyboardKeysNames.length ) - buttonText = Globals.AppTouchscreenKeyboardKeysNames[i - 2].replace("_", " "); - if( i == 0 ) - buttonText = "Joystick"; - if( i == 1 ) - buttonText = "Text input"; - if( i == 8 ) - buttonText = "Joystick 2"; - if( i == 9 ) - buttonText = "Joystick 3"; - p.setText(p.getResources().getString(R.string.screenkb_custom_layout_help) + "\n" + buttonText); - } + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(items[currentButton]); + builder.setSingleChoiceItems(SDL_Keys.namesSorted, SDL_Keys.namesSortedBackIdx[Globals.RemapScreenKbKeycode[currentButton]], new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.RemapScreenKbKeycode[currentButton] = SDL_Keys.namesSortedIdx[item]; - @Override - public boolean onTouch(View v, MotionEvent ev) - { - if( ev.getAction() == MotionEvent.ACTION_DOWN ) - { - oldX = (int)ev.getX(); - oldY = (int)ev.getY(); - resizing = true; - for( int i = 0; i < Globals.ScreenKbControlsLayout.length; i++ ) - { - if( ! Globals.ScreenKbControlsShown[i] ) - continue; - if( Globals.ScreenKbControlsLayout[i][0] <= oldX && - Globals.ScreenKbControlsLayout[i][2] >= oldX && - Globals.ScreenKbControlsLayout[i][1] <= oldY && - Globals.ScreenKbControlsLayout[i][3] >= oldY ) - { - currentButton = i; - setupButton(currentButton); - resizing = false; - break; - } - } - } - if( ev.getAction() == MotionEvent.ACTION_MOVE ) - { - int dx = (int)ev.getX() - oldX; - int dy = (int)ev.getY() - oldY; - if( resizing ) - { - // Resize slowly, with 1/3 of movement speed - dx /= 6; - dy /= 6; - if( Globals.ScreenKbControlsLayout[currentButton][0] <= Globals.ScreenKbControlsLayout[currentButton][2] + dx*2 ) - { - Globals.ScreenKbControlsLayout[currentButton][0] -= dx; - Globals.ScreenKbControlsLayout[currentButton][2] += dx; - } - if( Globals.ScreenKbControlsLayout[currentButton][1] <= Globals.ScreenKbControlsLayout[currentButton][3] + dy*2 ) - { - Globals.ScreenKbControlsLayout[currentButton][1] += dy; - Globals.ScreenKbControlsLayout[currentButton][3] -= dy; - } - dx *= 6; - dy *= 6; - } - else - { - Globals.ScreenKbControlsLayout[currentButton][0] += dx; - Globals.ScreenKbControlsLayout[currentButton][2] += dx; - Globals.ScreenKbControlsLayout[currentButton][1] += dy; - Globals.ScreenKbControlsLayout[currentButton][3] += dy; - } - oldX += dx; - oldY += dy; - Matrix m = new Matrix(); - RectF src = new RectF(0, 0, bmps[currentButton].getWidth(), bmps[currentButton].getHeight()); - RectF dst = new RectF(Globals.ScreenKbControlsLayout[currentButton][0], Globals.ScreenKbControlsLayout[currentButton][1], - Globals.ScreenKbControlsLayout[currentButton][2], Globals.ScreenKbControlsLayout[currentButton][3]); - m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); - imgs[currentButton].setImageMatrix(m); - m = new Matrix(); - src = new RectF(0, 0, boundaryBmp.getWidth(), boundaryBmp.getHeight()); - m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); - boundary.setImageMatrix(m); - } - return true; - } + dialog.dismiss(); + showRemapScreenKbConfig2(p, currentButton + 1); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) - { - if( keyCode == KeyEvent.KEYCODE_BACK ) - { - p.getVideoLayout().removeView(layout); - layout = null; - goBack(p); - } - return true; - } - } - } + static class ScreenGesturesConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.remap_screenkb_button_gestures); + } - static class ScreenKeyboardAdvanced extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.advanced); - } - //boolean enabled() { return true; }; - void run (final MainActivity p) - { - CharSequence[] items = { - p.getResources().getString(R.string.screenkb_floating_joystick), - }; + //boolean enabled() { return true; }; + void run(final MainActivity p) { + CharSequence[] items = { + p.getResources().getString(R.string.remap_screenkb_button_zoomin), + p.getResources().getString(R.string.remap_screenkb_button_zoomout), + p.getResources().getString(R.string.remap_screenkb_button_rotateleft), + p.getResources().getString(R.string.remap_screenkb_button_rotateright), + }; - boolean defaults[] = { - Globals.FloatingScreenJoystick, - }; - - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.advanced)); - builder.setMultiChoiceItems(items, defaults, new DialogInterface.OnMultiChoiceClickListener() - { - public void onClick(DialogInterface dialog, int item, boolean isChecked) - { - if( item == 0 ) - Globals.FloatingScreenJoystick = isChecked; - } - }); - builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + boolean defaults[] = { + Globals.MultitouchGesturesUsed[0], + Globals.MultitouchGesturesUsed[1], + Globals.MultitouchGesturesUsed[2], + Globals.MultitouchGesturesUsed[3], + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.remap_screenkb_button_gestures)); + builder.setMultiChoiceItems(items, defaults, new DialogInterface.OnMultiChoiceClickListener() { + public void onClick(DialogInterface dialog, int item, boolean isChecked) { + Globals.MultitouchGesturesUsed[item] = isChecked; + } + }); + builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); + showScreenGesturesConfig2(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + + static void showScreenGesturesConfig2(final MainActivity p) { + final CharSequence[] items = { + p.getResources().getString(R.string.accel_slow), + p.getResources().getString(R.string.accel_medium), + p.getResources().getString(R.string.accel_fast), + p.getResources().getString(R.string.accel_veryfast) + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.remap_screenkb_button_gestures_sensitivity); + builder.setSingleChoiceItems(items, Globals.MultitouchGestureSensitivity, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.MultitouchGestureSensitivity = item; + + dialog.dismiss(); + showScreenGesturesConfig3(p, 0); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + + static void showScreenGesturesConfig3(final MainActivity p, final int currentButton) { + CharSequence[] items = { + p.getResources().getString(R.string.remap_screenkb_button_zoomin), + p.getResources().getString(R.string.remap_screenkb_button_zoomout), + p.getResources().getString(R.string.remap_screenkb_button_rotateleft), + p.getResources().getString(R.string.remap_screenkb_button_rotateright), + }; + + if (currentButton >= Globals.RemapMultitouchGestureKeycode.length) { + goBack(p); + return; + } + if (!Globals.MultitouchGesturesUsed[currentButton]) { + showScreenGesturesConfig3(p, currentButton + 1); + return; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(items[currentButton]); + builder.setSingleChoiceItems(SDL_Keys.namesSorted, SDL_Keys.namesSortedBackIdx[Globals.RemapMultitouchGestureKeycode[currentButton]], new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.RemapMultitouchGestureKeycode[currentButton] = SDL_Keys.namesSortedIdx[item]; + + dialog.dismiss(); + showScreenGesturesConfig3(p, currentButton + 1); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } + + static class CustomizeScreenKbLayout extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.screenkb_custom_layout); + } + + //boolean enabled() { return true; }; + void run(final MainActivity p) { + p.setText(p.getResources().getString(R.string.screenkb_custom_layout_help)); + CustomizeScreenKbLayoutTool tool = new CustomizeScreenKbLayoutTool(p); + Globals.TouchscreenKeyboardSize = Globals.TOUCHSCREEN_KEYBOARD_CUSTOM; + } + + static class CustomizeScreenKbLayoutTool implements View.OnTouchListener, View.OnKeyListener { + MainActivity p; + FrameLayout layout = null; + ImageView imgs[] = new ImageView[Globals.ScreenKbControlsLayout.length]; + Bitmap bmps[] = new Bitmap[Globals.ScreenKbControlsLayout.length]; + ImageView boundary = null; + Bitmap boundaryBmp = null; + int currentButton = 0; + int buttons[] = { + R.drawable.dpad, + R.drawable.keyboard, + R.drawable.b1, + R.drawable.b2, + R.drawable.b3, + R.drawable.b4, + R.drawable.b5, + R.drawable.b6, + R.drawable.dpad, + R.drawable.dpad + }; + int oldX = 0, oldY = 0; + boolean resizing = false; + + public CustomizeScreenKbLayoutTool(MainActivity _p) { + p = _p; + layout = new FrameLayout(p); + p.getVideoLayout().addView(layout); + layout.setFocusable(true); + layout.setFocusableInTouchMode(true); + layout.requestFocus(); + layout.setOnTouchListener(this); + layout.setOnKeyListener(this); + boundary = new ImageView(p); + boundary.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + boundary.setScaleType(ImageView.ScaleType.MATRIX); + boundaryBmp = BitmapFactory.decodeResource(p.getResources(), R.drawable.rectangle); + boundary.setImageBitmap(boundaryBmp); + layout.addView(boundary); + currentButton = -1; + if (Globals.TouchscreenKeyboardTheme == 2) { + buttons = new int[]{ + R.drawable.sun_dpad, + R.drawable.sun_keyboard, + R.drawable.sun_b1, + R.drawable.sun_b2, + R.drawable.sun_b3, + R.drawable.sun_b4, + R.drawable.sun_b5, + R.drawable.sun_b6, + R.drawable.sun_dpad, + R.drawable.sun_dpad + }; + } + + int displayX = 800; + int displayY = 480; + try { + DisplayMetrics dm = new DisplayMetrics(); + p.getWindowManager().getDefaultDisplay().getMetrics(dm); + displayX = dm.widthPixels; + displayY = dm.heightPixels; + } catch (Exception eeeee) { + } + + for (int i = 0; i < Globals.ScreenKbControlsLayout.length; i++) { + if (!Globals.ScreenKbControlsShown[i]) + continue; + if (currentButton == -1) + currentButton = i; + //Log.i("SDL", "Screen kb button " + i + " coords " + Globals.ScreenKbControlsLayout[i][0] + ":" + Globals.ScreenKbControlsLayout[i][1] + ":" + Globals.ScreenKbControlsLayout[i][2] + ":" + Globals.ScreenKbControlsLayout[i][3] ); + // Check if the button is off screen edge or shrunk to zero + if (Globals.ScreenKbControlsLayout[i][0] > Globals.ScreenKbControlsLayout[i][2] - displayY / 12) + Globals.ScreenKbControlsLayout[i][0] = Globals.ScreenKbControlsLayout[i][2] - displayY / 12; + if (Globals.ScreenKbControlsLayout[i][1] > Globals.ScreenKbControlsLayout[i][3] - displayY / 12) + Globals.ScreenKbControlsLayout[i][1] = Globals.ScreenKbControlsLayout[i][3] - displayY / 12; + if (Globals.ScreenKbControlsLayout[i][0] < Globals.ScreenKbControlsLayout[i][2] - displayY * 2 / 3) + Globals.ScreenKbControlsLayout[i][0] = Globals.ScreenKbControlsLayout[i][2] - displayY * 2 / 3; + if (Globals.ScreenKbControlsLayout[i][1] < Globals.ScreenKbControlsLayout[i][3] - displayY * 2 / 3) + Globals.ScreenKbControlsLayout[i][1] = Globals.ScreenKbControlsLayout[i][3] - displayY * 2 / 3; + if (Globals.ScreenKbControlsLayout[i][0] < 0) { + Globals.ScreenKbControlsLayout[i][2] += -Globals.ScreenKbControlsLayout[i][0]; + Globals.ScreenKbControlsLayout[i][0] = 0; + } + if (Globals.ScreenKbControlsLayout[i][2] > displayX) { + Globals.ScreenKbControlsLayout[i][0] -= Globals.ScreenKbControlsLayout[i][2] - displayX; + Globals.ScreenKbControlsLayout[i][2] = displayX; + } + if (Globals.ScreenKbControlsLayout[i][1] < 0) { + Globals.ScreenKbControlsLayout[i][3] += -Globals.ScreenKbControlsLayout[i][1]; + Globals.ScreenKbControlsLayout[i][1] = 0; + } + if (Globals.ScreenKbControlsLayout[i][3] > displayY) { + Globals.ScreenKbControlsLayout[i][1] -= Globals.ScreenKbControlsLayout[i][3] - displayY; + Globals.ScreenKbControlsLayout[i][3] = displayY; + } + //Log.i("SDL", "After bounds check coords " + Globals.ScreenKbControlsLayout[i][0] + ":" + Globals.ScreenKbControlsLayout[i][1] + ":" + Globals.ScreenKbControlsLayout[i][2] + ":" + Globals.ScreenKbControlsLayout[i][3] ); + + imgs[i] = new ImageView(p); + imgs[i].setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + imgs[i].setScaleType(ImageView.ScaleType.MATRIX); + bmps[i] = BitmapFactory.decodeResource(p.getResources(), buttons[i]); + imgs[i].setImageBitmap(bmps[i]); + imgs[i].setAlpha(128); + layout.addView(imgs[i]); + Matrix m = new Matrix(); + RectF src = new RectF(0, 0, bmps[i].getWidth(), bmps[i].getHeight()); + RectF dst = new RectF(Globals.ScreenKbControlsLayout[i][0], Globals.ScreenKbControlsLayout[i][1], + Globals.ScreenKbControlsLayout[i][2], Globals.ScreenKbControlsLayout[i][3]); + m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); + imgs[i].setImageMatrix(m); + } + boundary.bringToFront(); + if (currentButton == -1) + onKey(null, KeyEvent.KEYCODE_BACK, null); // All buttons disabled - do not show anything + else + setupButton(currentButton); + } + + void setupButton(int i) { + Matrix m = new Matrix(); + RectF src = new RectF(0, 0, bmps[i].getWidth(), bmps[i].getHeight()); + RectF dst = new RectF(Globals.ScreenKbControlsLayout[i][0], Globals.ScreenKbControlsLayout[i][1], + Globals.ScreenKbControlsLayout[i][2], Globals.ScreenKbControlsLayout[i][3]); + m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); + imgs[i].setImageMatrix(m); + m = new Matrix(); + src = new RectF(0, 0, boundaryBmp.getWidth(), boundaryBmp.getHeight()); + m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); + boundary.setImageMatrix(m); + String buttonText = ""; + if (i >= 2 && i <= 7) + buttonText = p.getResources().getString(R.string.remap_screenkb_button) + (i - 2); + if (i >= 2 && i - 2 < Globals.AppTouchscreenKeyboardKeysNames.length) + buttonText = Globals.AppTouchscreenKeyboardKeysNames[i - 2].replace("_", " "); + if (i == 0) + buttonText = "Joystick"; + if (i == 1) + buttonText = "Text input"; + if (i == 8) + buttonText = "Joystick 2"; + if (i == 9) + buttonText = "Joystick 3"; + p.setText(p.getResources().getString(R.string.screenkb_custom_layout_help) + "\n" + buttonText); + } + + @Override + public boolean onTouch(View v, MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + oldX = (int) ev.getX(); + oldY = (int) ev.getY(); + resizing = true; + for (int i = 0; i < Globals.ScreenKbControlsLayout.length; i++) { + if (!Globals.ScreenKbControlsShown[i]) + continue; + if (Globals.ScreenKbControlsLayout[i][0] <= oldX && + Globals.ScreenKbControlsLayout[i][2] >= oldX && + Globals.ScreenKbControlsLayout[i][1] <= oldY && + Globals.ScreenKbControlsLayout[i][3] >= oldY) { + currentButton = i; + setupButton(currentButton); + resizing = false; + break; + } + } + } + if (ev.getAction() == MotionEvent.ACTION_MOVE) { + int dx = (int) ev.getX() - oldX; + int dy = (int) ev.getY() - oldY; + if (resizing) { + // Resize slowly, with 1/3 of movement speed + dx /= 6; + dy /= 6; + if (Globals.ScreenKbControlsLayout[currentButton][0] <= Globals.ScreenKbControlsLayout[currentButton][2] + dx * 2) { + Globals.ScreenKbControlsLayout[currentButton][0] -= dx; + Globals.ScreenKbControlsLayout[currentButton][2] += dx; + } + if (Globals.ScreenKbControlsLayout[currentButton][1] <= Globals.ScreenKbControlsLayout[currentButton][3] + dy * 2) { + Globals.ScreenKbControlsLayout[currentButton][1] += dy; + Globals.ScreenKbControlsLayout[currentButton][3] -= dy; + } + dx *= 6; + dy *= 6; + } else { + Globals.ScreenKbControlsLayout[currentButton][0] += dx; + Globals.ScreenKbControlsLayout[currentButton][2] += dx; + Globals.ScreenKbControlsLayout[currentButton][1] += dy; + Globals.ScreenKbControlsLayout[currentButton][3] += dy; + } + oldX += dx; + oldY += dy; + Matrix m = new Matrix(); + RectF src = new RectF(0, 0, bmps[currentButton].getWidth(), bmps[currentButton].getHeight()); + RectF dst = new RectF(Globals.ScreenKbControlsLayout[currentButton][0], Globals.ScreenKbControlsLayout[currentButton][1], + Globals.ScreenKbControlsLayout[currentButton][2], Globals.ScreenKbControlsLayout[currentButton][3]); + m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); + imgs[currentButton].setImageMatrix(m); + m = new Matrix(); + src = new RectF(0, 0, boundaryBmp.getWidth(), boundaryBmp.getHeight()); + m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); + boundary.setImageMatrix(m); + } + return true; + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + p.getVideoLayout().removeView(layout); + layout = null; + goBack(p); + } + return true; + } + } + } + + static class ScreenKeyboardAdvanced extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.advanced); + } + + //boolean enabled() { return true; }; + void run(final MainActivity p) { + CharSequence[] items = { + p.getResources().getString(R.string.screenkb_floating_joystick), + }; + + boolean defaults[] = { + Globals.FloatingScreenJoystick, + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.advanced)); + builder.setMultiChoiceItems(items, defaults, new DialogInterface.OnMultiChoiceClickListener() { + public void onClick(DialogInterface dialog, int item, boolean isChecked) { + if (item == 0) + Globals.FloatingScreenJoystick = isChecked; + } + }); + builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } } diff --git a/Xorg/src/main/java/io/neoterm/SettingsMenuMisc.java b/Xorg/src/main/java/io/neoterm/SettingsMenuMisc.java index dc71e4f..f97ae2e 100644 --- a/Xorg/src/main/java/io/neoterm/SettingsMenuMisc.java +++ b/Xorg/src/main/java/io/neoterm/SettingsMenuMisc.java @@ -26,589 +26,508 @@ import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.StatFs; -import androidx.appcompat.app.AlertDialog; import android.text.InputType; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.TextView; +import android.widget.*; +import androidx.appcompat.app.AlertDialog; +import io.neoterm.xorg.R; import java.util.ArrayList; import java.util.Locale; -import io.neoterm.xorg.R; +class SettingsMenuMisc extends SettingsMenu { + static class DownloadConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.storage_question); + } -class SettingsMenuMisc extends SettingsMenu -{ - static class DownloadConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.storage_question); - } - void run (final MainActivity p) - { - long freeSdcard = 0; - long freePhone = 0; - try - { - StatFs phone = new StatFs(p.getFilesDir().getAbsolutePath()); - freePhone = (long)phone.getAvailableBlocks() * phone.getBlockSize() / 1024 / 1024; - StatFs sdcard = new StatFs(Settings.SdcardAppPath.get().bestPath(p)); - freeSdcard = (long)sdcard.getAvailableBlocks() * sdcard.getBlockSize() / 1024 / 1024; - } - catch(Exception e) {} + void run(final MainActivity p) { + long freeSdcard = 0; + long freePhone = 0; + try { + StatFs phone = new StatFs(p.getFilesDir().getAbsolutePath()); + freePhone = (long) phone.getAvailableBlocks() * phone.getBlockSize() / 1024 / 1024; + StatFs sdcard = new StatFs(Settings.SdcardAppPath.get().bestPath(p)); + freeSdcard = (long) sdcard.getAvailableBlocks() * sdcard.getBlockSize() / 1024 / 1024; + } catch (Exception e) { + } - final CharSequence[] items = { p.getResources().getString(R.string.storage_phone, freePhone), - p.getResources().getString(R.string.storage_sd, freeSdcard), - p.getResources().getString(R.string.storage_custom) }; - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.storage_question)); - builder.setSingleChoiceItems(items, Globals.DownloadToSdcard ? 1 : 0, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); + final CharSequence[] items = {p.getResources().getString(R.string.storage_phone, freePhone), + p.getResources().getString(R.string.storage_sd, freeSdcard), + p.getResources().getString(R.string.storage_custom)}; + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.storage_question)); + builder.setSingleChoiceItems(items, Globals.DownloadToSdcard ? 1 : 0, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); - if( item == 2 ) - showCustomDownloadDirConfig(p); - else - { - Globals.DownloadToSdcard = (item != 0); - Globals.DataDir = Globals.DownloadToSdcard ? - Settings.SdcardAppPath.get().bestPath(p) : - p.getFilesDir().getAbsolutePath(); - goBack(p); - } - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - static void showCustomDownloadDirConfig(final MainActivity p) - { - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.storage_custom)); + if (item == 2) + showCustomDownloadDirConfig(p); + else { + Globals.DownloadToSdcard = (item != 0); + Globals.DataDir = Globals.DownloadToSdcard ? + Settings.SdcardAppPath.get().bestPath(p) : + p.getFilesDir().getAbsolutePath(); + goBack(p); + } + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } - final EditText edit = new EditText(p); - edit.setFocusableInTouchMode(true); - edit.setFocusable(true); - edit.setText(Globals.DataDir); - builder.setView(edit); + static void showCustomDownloadDirConfig(final MainActivity p) { + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.storage_custom)); - builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.DataDir = edit.getText().toString(); - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + final EditText edit = new EditText(p); + edit.setFocusableInTouchMode(true); + edit.setFocusable(true); + edit.setText(Globals.DataDir); + builder.setView(edit); - static class OptionalDownloadConfig extends Menu - { - boolean firstStart = false; - OptionalDownloadConfig() - { - firstStart = true; - } - OptionalDownloadConfig(boolean firstStart) - { - this.firstStart = firstStart; - } - String title(final MainActivity p) - { - return p.getResources().getString(R.string.downloads); - } - void run (final MainActivity p) - { - String [] downloadFiles = Globals.DataDownloadUrl; - - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.downloads)); + builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.DataDir = edit.getText().toString(); + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - final int itemsIdx[] = new int[downloadFiles.length]; - ArrayList items = new ArrayList(); - ArrayList enabledItems = new ArrayList(); - for(int i = 0; i < downloadFiles.length; i++ ) - { - String item = new String(downloadFiles[i].split("[|]")[0]); - boolean enabled = false; - if( item.toString().indexOf("!") == 0 ) - { - item = item.toString().substring(1); - enabled = true; - } - if( item.toString().indexOf("!") == 0 ) // Download is mandatory - continue; - itemsIdx[items.size()] = i; - items.add(item); - enabledItems.add(enabled); - } + static class OptionalDownloadConfig extends Menu { + boolean firstStart = false; - if( Globals.OptionalDataDownload == null || Globals.OptionalDataDownload.length != downloadFiles.length ) - { - Globals.OptionalDataDownload = new boolean[downloadFiles.length]; - boolean oldFormat = true; - for( int i = 0; i < downloadFiles.length; i++ ) - { - if( downloadFiles[i].indexOf("!") == 0 ) - { - Globals.OptionalDataDownload[i] = true; - oldFormat = false; - } - } - if( oldFormat ) - Globals.OptionalDataDownload[0] = true; - } - if( enabledItems.size() <= 0 ) - { - goBack(p); - return; - } + OptionalDownloadConfig() { + firstStart = true; + } - // Convert Boolean[] to boolean[], meh - boolean[] enabledItems2 = new boolean[enabledItems.size()]; - for( int i = 0; i < enabledItems.size(); i++ ) - enabledItems2[i] = enabledItems.get(i); + OptionalDownloadConfig(boolean firstStart) { + this.firstStart = firstStart; + } - builder.setMultiChoiceItems(items.toArray(new CharSequence[0]), enabledItems2, new DialogInterface.OnMultiChoiceClickListener() - { - public void onClick(DialogInterface dialog, int item, boolean isChecked) - { - Globals.OptionalDataDownload[itemsIdx[item]] = isChecked; - } - }); - builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - goBack(p); - } - }); - if( firstStart ) - { - builder.setNegativeButton(p.getResources().getString(R.string.show_more_options), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - menuStack.clear(); - new MainMenu().run(p); - } - }); - } - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + String title(final MainActivity p) { + return p.getResources().getString(R.string.downloads); + } - static class AudioConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.audiobuf_question); - } - void run (final MainActivity p) - { - final CharSequence[] items = { p.getResources().getString(R.string.audiobuf_verysmall), - p.getResources().getString(R.string.audiobuf_small), - p.getResources().getString(R.string.audiobuf_medium), - p.getResources().getString(R.string.audiobuf_large) }; + void run(final MainActivity p) { + String[] downloadFiles = Globals.DataDownloadUrl; - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.audiobuf_question); - builder.setSingleChoiceItems(items, Globals.AudioBufferConfig, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.AudioBufferConfig = item; - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.downloads)); - static class VideoSettingsConfig extends Menu - { - static int debugMenuShowCount = 0; - String title(final MainActivity p) - { - return p.getResources().getString(R.string.video); - } - //boolean enabled() { return true; }; - void run (final MainActivity p) - { - debugMenuShowCount++; - CharSequence[] items = { - p.getResources().getString(R.string.mouse_keepaspectratio), - p.getResources().getString(R.string.video_smooth), - p.getResources().getString(R.string.video_immersive), - p.getResources().getString(R.string.video_orientation_autodetect), - p.getResources().getString(R.string.video_orientation_vertical), - p.getResources().getString(R.string.video_bpp_24), - p.getResources().getString(R.string.tv_borders), - }; - boolean defaults[] = { - Globals.KeepAspectRatio, - Globals.VideoLinearFilter, - Globals.ImmersiveMode, - Globals.AutoDetectOrientation, - !Globals.HorizontalOrientation, - Globals.VideoDepthBpp == 24, - Globals.TvBorders, - }; + final int itemsIdx[] = new int[downloadFiles.length]; + ArrayList items = new ArrayList(); + ArrayList enabledItems = new ArrayList(); + for (int i = 0; i < downloadFiles.length; i++) { + String item = new String(downloadFiles[i].split("[|]")[0]); + boolean enabled = false; + if (item.toString().indexOf("!") == 0) { + item = item.toString().substring(1); + enabled = true; + } + if (item.toString().indexOf("!") == 0) // Download is mandatory + continue; + itemsIdx[items.size()] = i; + items.add(item); + enabledItems.add(enabled); + } - if(Globals.SwVideoMode && !Globals.CompatibilityHacksVideo) - { - CharSequence[] items2 = { - p.getResources().getString(R.string.mouse_keepaspectratio), - p.getResources().getString(R.string.video_smooth), - p.getResources().getString(R.string.video_immersive), - p.getResources().getString(R.string.video_orientation_autodetect), - p.getResources().getString(R.string.video_orientation_vertical), - p.getResources().getString(R.string.video_bpp_24), - p.getResources().getString(R.string.tv_borders), - p.getResources().getString(R.string.video_separatethread), - }; - boolean defaults2[] = { - Globals.KeepAspectRatio, - Globals.VideoLinearFilter, - Globals.ImmersiveMode, - Globals.AutoDetectOrientation, - !Globals.HorizontalOrientation, - Globals.VideoDepthBpp == 24, - Globals.TvBorders, - Globals.MultiThreadedVideo, - }; - items = items2; - defaults = defaults2; - } + if (Globals.OptionalDataDownload == null || Globals.OptionalDataDownload.length != downloadFiles.length) { + Globals.OptionalDataDownload = new boolean[downloadFiles.length]; + boolean oldFormat = true; + for (int i = 0; i < downloadFiles.length; i++) { + if (downloadFiles[i].indexOf("!") == 0) { + Globals.OptionalDataDownload[i] = true; + oldFormat = false; + } + } + if (oldFormat) + Globals.OptionalDataDownload[0] = true; + } + if (enabledItems.size() <= 0) { + goBack(p); + return; + } - if(Globals.Using_SDL_1_3) - { - CharSequence[] items2 = { - p.getResources().getString(R.string.mouse_keepaspectratio), - }; - boolean defaults2[] = { - Globals.KeepAspectRatio, - }; - items = items2; - defaults = defaults2; - } + // Convert Boolean[] to boolean[], meh + boolean[] enabledItems2 = new boolean[enabledItems.size()]; + for (int i = 0; i < enabledItems.size(); i++) + enabledItems2[i] = enabledItems.get(i); - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.video)); - builder.setMultiChoiceItems(items, defaults, new DialogInterface.OnMultiChoiceClickListener() - { - public void onClick(DialogInterface dialog, int item, boolean isChecked) - { - if( item == 0 ) - Globals.KeepAspectRatio = isChecked; - if( item == 1 ) - Globals.VideoLinearFilter = isChecked; - if( item == 2 ) - Globals.ImmersiveMode = isChecked; - if( item == 3 ) - Globals.AutoDetectOrientation = isChecked; - if( item == 4 ) - Globals.HorizontalOrientation = !isChecked; - if( item == 5 ) - Globals.VideoDepthBpp = (isChecked ? 24 : 16); - if( item == 6 ) - Globals.TvBorders = isChecked; - if( item == 7 ) - Globals.MultiThreadedVideo = isChecked; - } - }); - builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + builder.setMultiChoiceItems(items.toArray(new CharSequence[0]), enabledItems2, new DialogInterface.OnMultiChoiceClickListener() { + public void onClick(DialogInterface dialog, int item, boolean isChecked) { + Globals.OptionalDataDownload[itemsIdx[item]] = isChecked; + } + }); + builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); + goBack(p); + } + }); + if (firstStart) { + builder.setNegativeButton(p.getResources().getString(R.string.show_more_options), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); + menuStack.clear(); + new MainMenu().run(p); + } + }); + } + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - static class ShowReadme extends Menu - { - String title(final MainActivity p) - { - return "Readme"; - } - boolean enabled() - { - return true; - } - void run (final MainActivity p) - { - String readmes[] = Globals.ReadmeText.split("\\^"); - String lang = new String(Locale.getDefault().getLanguage()) + ":"; - if( p.isRunningOnOUYA() ) - lang = "tv:"; - String readme = readmes[0]; - String buttonName = "", buttonUrl = ""; - for( String r: readmes ) - { - if( r.startsWith(lang) ) - readme = r.substring(lang.length()); - if( r.startsWith("button:") ) - { - buttonName = r.substring("button:".length()); - if( buttonName.indexOf(":") != -1 ) - { - buttonUrl = buttonName.substring(buttonName.indexOf(":") + 1); - buttonName = buttonName.substring(0, buttonName.indexOf(":")); - } - } - } - readme = readme.trim(); - if( readme.length() <= 2 ) - { - goBack(p); - return; - } - TextView text = new TextView(p); - text.setMaxLines(100); - //text.setScroller(new Scroller(p)); - //text.setVerticalScrollBarEnabled(true); - text.setText(readme); - text.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - text.setPadding(0, 5, 0, 20); - text.setTextSize(20.0f); - text.setGravity(Gravity.CENTER); - text.setFocusable(false); - text.setFocusableInTouchMode(false); - AlertDialog.Builder builder = new AlertDialog.Builder(p); - ScrollView scroll = new ScrollView(p); - scroll.setFocusable(false); - scroll.setFocusableInTouchMode(false); - scroll.addView(text, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - final Button ok = new Button(p); - final AlertDialog alertDismiss[] = new AlertDialog[1]; - ok.setOnClickListener(new View.OnClickListener() - { - public void onClick(View v) - { - alertDismiss[0].cancel(); - } - }); - ok.setText(R.string.ok); - LinearLayout layout = new LinearLayout(p); - layout.setOrientation(LinearLayout.VERTICAL); - layout.addView(scroll); - layout.addView(ok); - if( buttonName.length() > 0 ) - { - Button cancel = new Button(p); - cancel.setText(buttonName); - final String url = buttonUrl; - cancel.setOnClickListener(new View.OnClickListener() - { - public void onClick(View v) - { - if( url.length() > 0 ) - { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(url)); - p.startActivity(i); - } - alertDismiss[0].cancel(); - System.exit(0); - } - }); - layout.addView(cancel); - } - builder.setView(layout); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alertDismiss[0] = alert; - alert.setOwnerActivity(p); - alert.show(); - } - } + static class AudioConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.audiobuf_question); + } - static class GyroscopeCalibration extends Menu - { - String title(final MainActivity p) - { - return ""; - } - boolean enabled() - { - return false; - } - void run (final MainActivity p) - { - goBack(p); - } - } + void run(final MainActivity p) { + final CharSequence[] items = {p.getResources().getString(R.string.audiobuf_verysmall), + p.getResources().getString(R.string.audiobuf_small), + p.getResources().getString(R.string.audiobuf_medium), + p.getResources().getString(R.string.audiobuf_large)}; - static class CommandlineConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.storage_commandline); - } - void run (final MainActivity p) - { - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.storage_commandline)); + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.audiobuf_question); + builder.setSingleChoiceItems(items, Globals.AudioBufferConfig, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.AudioBufferConfig = item; + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - final EditText edit = new EditText(p); - edit.setFocusableInTouchMode(true); - edit.setFocusable(true); - if (Globals.CommandLine.length() == 0) - Globals.CommandLine = "SDL_app"; - if (Globals.CommandLine.indexOf(" ") == -1) - Globals.CommandLine += " "; - edit.setText(Globals.CommandLine.substring(Globals.CommandLine.indexOf(" ")).replace(" ", "\n").replace(" ", " ")); - edit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - edit.setMinLines(2); - //edit.setMaxLines(100); - builder.setView(edit); + static class VideoSettingsConfig extends Menu { + static int debugMenuShowCount = 0; - builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.CommandLine = "SDL_app"; - String args[] = edit.getText().toString().split("\n"); - boolean firstArg = true; - for( String arg: args ) - { - Globals.CommandLine += " "; - if( firstArg ) - Globals.CommandLine += arg; - else - Globals.CommandLine += arg.replace(" ", " "); - firstArg = false; - } - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + String title(final MainActivity p) { + return p.getResources().getString(R.string.video); + } - static class ResetToDefaultsConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.reset_config); - } - boolean enabled() - { - return true; - } - void run (final MainActivity p) - { - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.reset_config_ask)); - builder.setMessage(p.getResources().getString(R.string.reset_config_ask)); - - builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Settings.DeleteSdlConfigOnUpgradeAndRestart(p); // Never returns - dialog.dismiss(); - goBack(p); - } - }); - builder.setNegativeButton(p.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + //boolean enabled() { return true; }; + void run(final MainActivity p) { + debugMenuShowCount++; + CharSequence[] items = { + p.getResources().getString(R.string.mouse_keepaspectratio), + p.getResources().getString(R.string.video_smooth), + p.getResources().getString(R.string.video_immersive), + p.getResources().getString(R.string.video_orientation_autodetect), + p.getResources().getString(R.string.video_orientation_vertical), + p.getResources().getString(R.string.video_bpp_24), + p.getResources().getString(R.string.tv_borders), + }; + boolean defaults[] = { + Globals.KeepAspectRatio, + Globals.VideoLinearFilter, + Globals.ImmersiveMode, + Globals.AutoDetectOrientation, + !Globals.HorizontalOrientation, + Globals.VideoDepthBpp == 24, + Globals.TvBorders, + }; + + if (Globals.SwVideoMode && !Globals.CompatibilityHacksVideo) { + CharSequence[] items2 = { + p.getResources().getString(R.string.mouse_keepaspectratio), + p.getResources().getString(R.string.video_smooth), + p.getResources().getString(R.string.video_immersive), + p.getResources().getString(R.string.video_orientation_autodetect), + p.getResources().getString(R.string.video_orientation_vertical), + p.getResources().getString(R.string.video_bpp_24), + p.getResources().getString(R.string.tv_borders), + p.getResources().getString(R.string.video_separatethread), + }; + boolean defaults2[] = { + Globals.KeepAspectRatio, + Globals.VideoLinearFilter, + Globals.ImmersiveMode, + Globals.AutoDetectOrientation, + !Globals.HorizontalOrientation, + Globals.VideoDepthBpp == 24, + Globals.TvBorders, + Globals.MultiThreadedVideo, + }; + items = items2; + defaults = defaults2; + } + + if (Globals.Using_SDL_1_3) { + CharSequence[] items2 = { + p.getResources().getString(R.string.mouse_keepaspectratio), + }; + boolean defaults2[] = { + Globals.KeepAspectRatio, + }; + items = items2; + defaults = defaults2; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.video)); + builder.setMultiChoiceItems(items, defaults, new DialogInterface.OnMultiChoiceClickListener() { + public void onClick(DialogInterface dialog, int item, boolean isChecked) { + if (item == 0) + Globals.KeepAspectRatio = isChecked; + if (item == 1) + Globals.VideoLinearFilter = isChecked; + if (item == 2) + Globals.ImmersiveMode = isChecked; + if (item == 3) + Globals.AutoDetectOrientation = isChecked; + if (item == 4) + Globals.HorizontalOrientation = !isChecked; + if (item == 5) + Globals.VideoDepthBpp = (isChecked ? 24 : 16); + if (item == 6) + Globals.TvBorders = isChecked; + if (item == 7) + Globals.MultiThreadedVideo = isChecked; + } + }); + builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } + + static class ShowReadme extends Menu { + String title(final MainActivity p) { + return "Readme"; + } + + boolean enabled() { + return true; + } + + void run(final MainActivity p) { + String readmes[] = Globals.ReadmeText.split("\\^"); + String lang = new String(Locale.getDefault().getLanguage()) + ":"; + if (p.isRunningOnOUYA()) + lang = "tv:"; + String readme = readmes[0]; + String buttonName = "", buttonUrl = ""; + for (String r : readmes) { + if (r.startsWith(lang)) + readme = r.substring(lang.length()); + if (r.startsWith("button:")) { + buttonName = r.substring("button:".length()); + if (buttonName.indexOf(":") != -1) { + buttonUrl = buttonName.substring(buttonName.indexOf(":") + 1); + buttonName = buttonName.substring(0, buttonName.indexOf(":")); + } + } + } + readme = readme.trim(); + if (readme.length() <= 2) { + goBack(p); + return; + } + TextView text = new TextView(p); + text.setMaxLines(100); + //text.setScroller(new Scroller(p)); + //text.setVerticalScrollBarEnabled(true); + text.setText(readme); + text.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + text.setPadding(0, 5, 0, 20); + text.setTextSize(20.0f); + text.setGravity(Gravity.CENTER); + text.setFocusable(false); + text.setFocusableInTouchMode(false); + AlertDialog.Builder builder = new AlertDialog.Builder(p); + ScrollView scroll = new ScrollView(p); + scroll.setFocusable(false); + scroll.setFocusableInTouchMode(false); + scroll.addView(text, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + final Button ok = new Button(p); + final AlertDialog alertDismiss[] = new AlertDialog[1]; + ok.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + alertDismiss[0].cancel(); + } + }); + ok.setText(R.string.ok); + LinearLayout layout = new LinearLayout(p); + layout.setOrientation(LinearLayout.VERTICAL); + layout.addView(scroll); + layout.addView(ok); + if (buttonName.length() > 0) { + Button cancel = new Button(p); + cancel.setText(buttonName); + final String url = buttonUrl; + cancel.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + if (url.length() > 0) { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(url)); + p.startActivity(i); + } + alertDismiss[0].cancel(); + System.exit(0); + } + }); + layout.addView(cancel); + } + builder.setView(layout); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alertDismiss[0] = alert; + alert.setOwnerActivity(p); + alert.show(); + } + } + + static class GyroscopeCalibration extends Menu { + String title(final MainActivity p) { + return ""; + } + + boolean enabled() { + return false; + } + + void run(final MainActivity p) { + goBack(p); + } + } + + static class CommandlineConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.storage_commandline); + } + + void run(final MainActivity p) { + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.storage_commandline)); + + final EditText edit = new EditText(p); + edit.setFocusableInTouchMode(true); + edit.setFocusable(true); + if (Globals.CommandLine.length() == 0) + Globals.CommandLine = "SDL_app"; + if (Globals.CommandLine.indexOf(" ") == -1) + Globals.CommandLine += " "; + edit.setText(Globals.CommandLine.substring(Globals.CommandLine.indexOf(" ")).replace(" ", "\n").replace(" ", " ")); + edit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + edit.setMinLines(2); + //edit.setMaxLines(100); + builder.setView(edit); + + builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.CommandLine = "SDL_app"; + String args[] = edit.getText().toString().split("\n"); + boolean firstArg = true; + for (String arg : args) { + Globals.CommandLine += " "; + if (firstArg) + Globals.CommandLine += arg; + else + Globals.CommandLine += arg.replace(" ", " "); + firstArg = false; + } + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } + + static class ResetToDefaultsConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.reset_config); + } + + boolean enabled() { + return true; + } + + void run(final MainActivity p) { + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.reset_config_ask)); + builder.setMessage(p.getResources().getString(R.string.reset_config_ask)); + + builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Settings.DeleteSdlConfigOnUpgradeAndRestart(p); // Never returns + dialog.dismiss(); + goBack(p); + } + }); + builder.setNegativeButton(p.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } } diff --git a/Xorg/src/main/java/io/neoterm/SettingsMenuMouse.java b/Xorg/src/main/java/io/neoterm/SettingsMenuMouse.java index 438340c..9d4994b 100644 --- a/Xorg/src/main/java/io/neoterm/SettingsMenuMouse.java +++ b/Xorg/src/main/java/io/neoterm/SettingsMenuMouse.java @@ -27,164 +27,149 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.RectF; -import androidx.appcompat.app.AlertDialog; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import androidx.appcompat.app.AlertDialog; +import io.neoterm.xorg.R; import java.util.ArrayList; -import io.neoterm.xorg.R; +class SettingsMenuMouse extends SettingsMenu { + static class MouseConfigMainMenu extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.mouse_emulation); + } -class SettingsMenuMouse extends SettingsMenu -{ - static class MouseConfigMainMenu extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.mouse_emulation); - } - boolean enabled() - { - return Globals.AppUsesMouse; - } - void run (final MainActivity p) - { - Menu options[] = - { - new DisplaySizeConfig(false), - new LeftClickConfig(), - new RightClickConfig(), - new AdditionalMouseConfig(), - new JoystickMouseConfig(), - new TouchPressureMeasurementTool(), - new CalibrateTouchscreenMenu(), - new OkButton(), - }; - showMenuOptionsList(p, options); - } - } + boolean enabled() { + return Globals.AppUsesMouse; + } - static class DisplaySizeConfig extends Menu - { - boolean firstStart = false; - DisplaySizeConfig() - { - this.firstStart = true; - } - DisplaySizeConfig(boolean firstStart) - { - this.firstStart = firstStart; - } - String title(final MainActivity p) - { - return p.getResources().getString(R.string.display_size_mouse); - } - void run (final MainActivity p) - { - CharSequence[] items = { - p.getResources().getString(R.string.display_size_small), - p.getResources().getString(R.string.display_size_small_touchpad), - p.getResources().getString(R.string.display_size_large), - p.getResources().getString(R.string.display_size_desktop), - }; - int _size_small = 0; - int _size_small_touchpad = 1; - int _size_large = 2; - int _size_desktop = 3; - int _more_options = 4; + void run(final MainActivity p) { + Menu options[] = + { + new DisplaySizeConfig(false), + new LeftClickConfig(), + new RightClickConfig(), + new AdditionalMouseConfig(), + new JoystickMouseConfig(), + new TouchPressureMeasurementTool(), + new CalibrateTouchscreenMenu(), + new OkButton(), + }; + showMenuOptionsList(p, options); + } + } - if( ! Globals.SwVideoMode ) - { - CharSequence[] items2 = { - p.getResources().getString(R.string.display_size_small_touchpad), - p.getResources().getString(R.string.display_size_large), - p.getResources().getString(R.string.display_size_desktop), - }; - items = items2; - _size_small_touchpad = 0; - _size_large = 1; - _size_desktop = 2; - _size_small = 1000; - } - if( firstStart ) - { - CharSequence[] items2 = { - p.getResources().getString(R.string.display_size_small), - p.getResources().getString(R.string.display_size_small_touchpad), - p.getResources().getString(R.string.display_size_large), - p.getResources().getString(R.string.display_size_desktop), - p.getResources().getString(R.string.show_more_options), - }; - items = items2; - if( ! Globals.SwVideoMode ) - { - CharSequence[] items3 = { - p.getResources().getString(R.string.display_size_small_touchpad), - p.getResources().getString(R.string.display_size_large), - p.getResources().getString(R.string.display_size_desktop), - p.getResources().getString(R.string.show_more_options), - }; - items = items3; - _more_options = 3; - } - } - // Java is so damn worse than C++11 - final int size_small = _size_small; - final int size_small_touchpad = _size_small_touchpad; - final int size_large = _size_large; - final int size_desktop = _size_desktop; - final int more_options = _more_options; + static class DisplaySizeConfig extends Menu { + boolean firstStart = false; - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.display_size); - class ClickListener implements DialogInterface.OnClickListener - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - if( item == size_desktop ) - { - Globals.LeftClickMethod = Mouse.LEFT_CLICK_NORMAL; - Globals.RelativeMouseMovement = false; - Globals.ShowScreenUnderFinger = Mouse.ZOOM_NONE; - Globals.ForceHardwareMouse = true; - } - if( item == size_large ) - { - Globals.LeftClickMethod = Mouse.LEFT_CLICK_NORMAL; - Globals.RelativeMouseMovement = false; - Globals.ShowScreenUnderFinger = Mouse.ZOOM_NONE; - Globals.ForceHardwareMouse = false; - } - if( item == size_small ) - { - Globals.LeftClickMethod = Mouse.LEFT_CLICK_NEAR_CURSOR; - Globals.RelativeMouseMovement = false; - Globals.ShowScreenUnderFinger = Mouse.ZOOM_MAGNIFIER; - Globals.ForceHardwareMouse = false; - } - if( item == size_small_touchpad ) - { - Globals.LeftClickMethod = Mouse.LEFT_CLICK_WITH_TAP_OR_TIMEOUT; - Globals.RelativeMouseMovement = true; - Globals.ShowScreenUnderFinger = Mouse.ZOOM_NONE; - Globals.ForceHardwareMouse = false; - } - if( item == more_options ) - { - menuStack.clear(); - new MainMenu().run(p); - return; - } - goBack(p); - } - } - builder.setItems(items, new ClickListener()); + DisplaySizeConfig() { + this.firstStart = true; + } + + DisplaySizeConfig(boolean firstStart) { + this.firstStart = firstStart; + } + + String title(final MainActivity p) { + return p.getResources().getString(R.string.display_size_mouse); + } + + void run(final MainActivity p) { + CharSequence[] items = { + p.getResources().getString(R.string.display_size_small), + p.getResources().getString(R.string.display_size_small_touchpad), + p.getResources().getString(R.string.display_size_large), + p.getResources().getString(R.string.display_size_desktop), + }; + int _size_small = 0; + int _size_small_touchpad = 1; + int _size_large = 2; + int _size_desktop = 3; + int _more_options = 4; + + if (!Globals.SwVideoMode) { + CharSequence[] items2 = { + p.getResources().getString(R.string.display_size_small_touchpad), + p.getResources().getString(R.string.display_size_large), + p.getResources().getString(R.string.display_size_desktop), + }; + items = items2; + _size_small_touchpad = 0; + _size_large = 1; + _size_desktop = 2; + _size_small = 1000; + } + if (firstStart) { + CharSequence[] items2 = { + p.getResources().getString(R.string.display_size_small), + p.getResources().getString(R.string.display_size_small_touchpad), + p.getResources().getString(R.string.display_size_large), + p.getResources().getString(R.string.display_size_desktop), + p.getResources().getString(R.string.show_more_options), + }; + items = items2; + if (!Globals.SwVideoMode) { + CharSequence[] items3 = { + p.getResources().getString(R.string.display_size_small_touchpad), + p.getResources().getString(R.string.display_size_large), + p.getResources().getString(R.string.display_size_desktop), + p.getResources().getString(R.string.show_more_options), + }; + items = items3; + _more_options = 3; + } + } + // Java is so damn worse than C++11 + final int size_small = _size_small; + final int size_small_touchpad = _size_small_touchpad; + final int size_large = _size_large; + final int size_desktop = _size_desktop; + final int more_options = _more_options; + + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.display_size); + class ClickListener implements DialogInterface.OnClickListener { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); + if (item == size_desktop) { + Globals.LeftClickMethod = Mouse.LEFT_CLICK_NORMAL; + Globals.RelativeMouseMovement = false; + Globals.ShowScreenUnderFinger = Mouse.ZOOM_NONE; + Globals.ForceHardwareMouse = true; + } + if (item == size_large) { + Globals.LeftClickMethod = Mouse.LEFT_CLICK_NORMAL; + Globals.RelativeMouseMovement = false; + Globals.ShowScreenUnderFinger = Mouse.ZOOM_NONE; + Globals.ForceHardwareMouse = false; + } + if (item == size_small) { + Globals.LeftClickMethod = Mouse.LEFT_CLICK_NEAR_CURSOR; + Globals.RelativeMouseMovement = false; + Globals.ShowScreenUnderFinger = Mouse.ZOOM_MAGNIFIER; + Globals.ForceHardwareMouse = false; + } + if (item == size_small_touchpad) { + Globals.LeftClickMethod = Mouse.LEFT_CLICK_WITH_TAP_OR_TIMEOUT; + Globals.RelativeMouseMovement = true; + Globals.ShowScreenUnderFinger = Mouse.ZOOM_NONE; + Globals.ForceHardwareMouse = false; + } + if (item == more_options) { + menuStack.clear(); + new MainMenu().run(p); + return; + } + goBack(p); + } + } + builder.setItems(items, new ClickListener()); /* else builder.setSingleChoiceItems(items, @@ -194,604 +179,534 @@ class SettingsMenuMouse extends SettingsMenu Globals.ShowScreenUnderFinger + 1, new ClickListener()); */ - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - static class LeftClickConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.leftclick_question); - } - void run (final MainActivity p) - { - final CharSequence[] items = { p.getResources().getString(R.string.leftclick_normal), - p.getResources().getString(R.string.leftclick_near_cursor), - p.getResources().getString(R.string.leftclick_multitouch), - p.getResources().getString(R.string.leftclick_pressure), - p.getResources().getString(R.string.rightclick_key), - p.getResources().getString(R.string.leftclick_timeout), - p.getResources().getString(R.string.leftclick_tap), - p.getResources().getString(R.string.leftclick_tap_or_timeout) }; + static class LeftClickConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.leftclick_question); + } - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.leftclick_question); - builder.setSingleChoiceItems(items, Globals.LeftClickMethod, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - Globals.LeftClickMethod = item; - if( item == Mouse.LEFT_CLICK_WITH_KEY ) - p.getVideoLayout().setOnKeyListener(new KeyRemapToolMouseClick(p, true)); - else if( item == Mouse.LEFT_CLICK_WITH_TIMEOUT || item == Mouse.LEFT_CLICK_WITH_TAP_OR_TIMEOUT ) - showLeftClickTimeoutConfig(p); - else - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - static void showLeftClickTimeoutConfig(final MainActivity p) { - final CharSequence[] items = { p.getResources().getString(R.string.leftclick_timeout_time_0), - p.getResources().getString(R.string.leftclick_timeout_time_1), - p.getResources().getString(R.string.leftclick_timeout_time_2), - p.getResources().getString(R.string.leftclick_timeout_time_3), - p.getResources().getString(R.string.leftclick_timeout_time_4) }; + void run(final MainActivity p) { + final CharSequence[] items = {p.getResources().getString(R.string.leftclick_normal), + p.getResources().getString(R.string.leftclick_near_cursor), + p.getResources().getString(R.string.leftclick_multitouch), + p.getResources().getString(R.string.leftclick_pressure), + p.getResources().getString(R.string.rightclick_key), + p.getResources().getString(R.string.leftclick_timeout), + p.getResources().getString(R.string.leftclick_tap), + p.getResources().getString(R.string.leftclick_tap_or_timeout)}; - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.leftclick_timeout_time); - builder.setSingleChoiceItems(items, Globals.LeftClickTimeout, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.LeftClickTimeout = item; - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.leftclick_question); + builder.setSingleChoiceItems(items, Globals.LeftClickMethod, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); + Globals.LeftClickMethod = item; + if (item == Mouse.LEFT_CLICK_WITH_KEY) + p.getVideoLayout().setOnKeyListener(new KeyRemapToolMouseClick(p, true)); + else if (item == Mouse.LEFT_CLICK_WITH_TIMEOUT || item == Mouse.LEFT_CLICK_WITH_TAP_OR_TIMEOUT) + showLeftClickTimeoutConfig(p); + else + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } - static class RightClickConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.rightclick_question); - } - boolean enabled() - { - return Globals.AppNeedsTwoButtonMouse; - } - void run (final MainActivity p) - { - final CharSequence[] items = { p.getResources().getString(R.string.rightclick_none), - p.getResources().getString(R.string.rightclick_multitouch), - p.getResources().getString(R.string.rightclick_pressure), - p.getResources().getString(R.string.rightclick_key), - p.getResources().getString(R.string.leftclick_timeout) }; + static void showLeftClickTimeoutConfig(final MainActivity p) { + final CharSequence[] items = {p.getResources().getString(R.string.leftclick_timeout_time_0), + p.getResources().getString(R.string.leftclick_timeout_time_1), + p.getResources().getString(R.string.leftclick_timeout_time_2), + p.getResources().getString(R.string.leftclick_timeout_time_3), + p.getResources().getString(R.string.leftclick_timeout_time_4)}; - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.rightclick_question); - builder.setSingleChoiceItems(items, Globals.RightClickMethod, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.RightClickMethod = item; - dialog.dismiss(); - if( item == Mouse.RIGHT_CLICK_WITH_KEY ) - p.getVideoLayout().setOnKeyListener(new KeyRemapToolMouseClick(p, false)); - else if( item == Mouse.RIGHT_CLICK_WITH_TIMEOUT ) - showRightClickTimeoutConfig(p); - else - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.leftclick_timeout_time); + builder.setSingleChoiceItems(items, Globals.LeftClickTimeout, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.LeftClickTimeout = item; + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - static void showRightClickTimeoutConfig(final MainActivity p) { - final CharSequence[] items = { p.getResources().getString(R.string.leftclick_timeout_time_0), - p.getResources().getString(R.string.leftclick_timeout_time_1), - p.getResources().getString(R.string.leftclick_timeout_time_2), - p.getResources().getString(R.string.leftclick_timeout_time_3), - p.getResources().getString(R.string.leftclick_timeout_time_4) }; + static class RightClickConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.rightclick_question); + } - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.leftclick_timeout_time); - builder.setSingleChoiceItems(items, Globals.RightClickTimeout, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.RightClickTimeout = item; - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + boolean enabled() { + return Globals.AppNeedsTwoButtonMouse; + } - public static class KeyRemapToolMouseClick implements View.OnKeyListener - { - MainActivity p; - boolean leftClick; - public KeyRemapToolMouseClick(MainActivity _p, boolean leftClick) - { - p = _p; - p.setText(p.getResources().getString(R.string.remap_hwkeys_press)); - this.leftClick = leftClick; - } - - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) - { - p.getVideoLayout().setOnKeyListener(null); - int keyIndex = keyCode; - if( keyIndex < 0 ) - keyIndex = 0; - if( keyIndex > SDL_Keys.JAVA_KEYCODE_LAST ) - keyIndex = 0; + void run(final MainActivity p) { + final CharSequence[] items = {p.getResources().getString(R.string.rightclick_none), + p.getResources().getString(R.string.rightclick_multitouch), + p.getResources().getString(R.string.rightclick_pressure), + p.getResources().getString(R.string.rightclick_key), + p.getResources().getString(R.string.leftclick_timeout)}; - if( leftClick ) - Globals.LeftClickKey = keyIndex; - else - Globals.RightClickKey = keyIndex; + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.rightclick_question); + builder.setSingleChoiceItems(items, Globals.RightClickMethod, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.RightClickMethod = item; + dialog.dismiss(); + if (item == Mouse.RIGHT_CLICK_WITH_KEY) + p.getVideoLayout().setOnKeyListener(new KeyRemapToolMouseClick(p, false)); + else if (item == Mouse.RIGHT_CLICK_WITH_TIMEOUT) + showRightClickTimeoutConfig(p); + else + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } - goBack(p); - return true; - } - } + static void showRightClickTimeoutConfig(final MainActivity p) { + final CharSequence[] items = {p.getResources().getString(R.string.leftclick_timeout_time_0), + p.getResources().getString(R.string.leftclick_timeout_time_1), + p.getResources().getString(R.string.leftclick_timeout_time_2), + p.getResources().getString(R.string.leftclick_timeout_time_3), + p.getResources().getString(R.string.leftclick_timeout_time_4)}; - static class AdditionalMouseConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.advanced); - } - void run (final MainActivity p) - { - CharSequence[] items = { - p.getResources().getString(R.string.mouse_hover_jitter_filter), - p.getResources().getString(R.string.mouse_joystickmouse), - p.getResources().getString(R.string.click_with_dpadcenter), - p.getResources().getString(R.string.mouse_relative), - p.getResources().getString(R.string.mouse_gyroscope_mouse), - p.getResources().getString(R.string.mouse_finger_hover), - p.getResources().getString(R.string.mouse_subframe_touch_events), - }; + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.leftclick_timeout_time); + builder.setSingleChoiceItems(items, Globals.RightClickTimeout, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.RightClickTimeout = item; + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - boolean defaults[] = { - Globals.HoverJitterFilter, - Globals.MoveMouseWithJoystick, - Globals.ClickMouseWithDpad, - Globals.RelativeMouseMovement, - Globals.MoveMouseWithGyroscope, - Globals.FingerHover, - Globals.GenerateSubframeTouchEvents, - }; + public static class KeyRemapToolMouseClick implements View.OnKeyListener { + MainActivity p; + boolean leftClick; - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.advanced)); - builder.setMultiChoiceItems(items, defaults, new DialogInterface.OnMultiChoiceClickListener() - { - public void onClick(DialogInterface dialog, int item, boolean isChecked) - { - if( item == 0 ) - Globals.HoverJitterFilter = isChecked; - if( item == 1 ) - Globals.MoveMouseWithJoystick = isChecked; - if( item == 2 ) - Globals.ClickMouseWithDpad = isChecked; - if( item == 3 ) - Globals.RelativeMouseMovement = isChecked; - if( item == 4 ) - Globals.MoveMouseWithGyroscope = isChecked; - if( item == 5 ) - Globals.FingerHover = isChecked; - if( item == 6 ) - Globals.GenerateSubframeTouchEvents = isChecked; - } - }); - builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - showGyroscopeMouseMovementConfig(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } + public KeyRemapToolMouseClick(MainActivity _p, boolean leftClick) { + p = _p; + p.setText(p.getResources().getString(R.string.remap_hwkeys_press)); + this.leftClick = leftClick; + } - static void showGyroscopeMouseMovementConfig(final MainActivity p) - { - if( !Globals.MoveMouseWithGyroscope ) - { - showRelativeMouseMovementConfig(p); - return; - } + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + p.getVideoLayout().setOnKeyListener(null); + int keyIndex = keyCode; + if (keyIndex < 0) + keyIndex = 0; + if (keyIndex > SDL_Keys.JAVA_KEYCODE_LAST) + keyIndex = 0; - final CharSequence[] items = { p.getResources().getString(R.string.accel_veryslow), - p.getResources().getString(R.string.accel_slow), - p.getResources().getString(R.string.accel_medium), - p.getResources().getString(R.string.accel_fast), - p.getResources().getString(R.string.accel_veryfast) }; + if (leftClick) + Globals.LeftClickKey = keyIndex; + else + Globals.RightClickKey = keyIndex; - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.mouse_gyroscope_mouse_sensitivity); - builder.setSingleChoiceItems(items, Globals.MoveMouseWithGyroscopeSpeed, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.MoveMouseWithGyroscopeSpeed = item; + goBack(p); + return true; + } + } - dialog.dismiss(); - showRelativeMouseMovementConfig(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } + static class AdditionalMouseConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.advanced); + } - static void showRelativeMouseMovementConfig(final MainActivity p) - { - if( !Globals.RelativeMouseMovement ) - { - goBack(p); - return; - } + void run(final MainActivity p) { + CharSequence[] items = { + p.getResources().getString(R.string.mouse_hover_jitter_filter), + p.getResources().getString(R.string.mouse_joystickmouse), + p.getResources().getString(R.string.click_with_dpadcenter), + p.getResources().getString(R.string.mouse_relative), + p.getResources().getString(R.string.mouse_gyroscope_mouse), + p.getResources().getString(R.string.mouse_finger_hover), + p.getResources().getString(R.string.mouse_subframe_touch_events), + }; - final CharSequence[] items = { p.getResources().getString(R.string.accel_veryslow), - p.getResources().getString(R.string.accel_slow), - p.getResources().getString(R.string.accel_medium), - p.getResources().getString(R.string.accel_fast), - p.getResources().getString(R.string.accel_veryfast) }; + boolean defaults[] = { + Globals.HoverJitterFilter, + Globals.MoveMouseWithJoystick, + Globals.ClickMouseWithDpad, + Globals.RelativeMouseMovement, + Globals.MoveMouseWithGyroscope, + Globals.FingerHover, + Globals.GenerateSubframeTouchEvents, + }; - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.mouse_relative_speed); - builder.setSingleChoiceItems(items, Globals.RelativeMouseMovementSpeed, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.RelativeMouseMovementSpeed = item; + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(p.getResources().getString(R.string.advanced)); + builder.setMultiChoiceItems(items, defaults, new DialogInterface.OnMultiChoiceClickListener() { + public void onClick(DialogInterface dialog, int item, boolean isChecked) { + if (item == 0) + Globals.HoverJitterFilter = isChecked; + if (item == 1) + Globals.MoveMouseWithJoystick = isChecked; + if (item == 2) + Globals.ClickMouseWithDpad = isChecked; + if (item == 3) + Globals.RelativeMouseMovement = isChecked; + if (item == 4) + Globals.MoveMouseWithGyroscope = isChecked; + if (item == 5) + Globals.FingerHover = isChecked; + if (item == 6) + Globals.GenerateSubframeTouchEvents = isChecked; + } + }); + builder.setPositiveButton(p.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + dialog.dismiss(); + showGyroscopeMouseMovementConfig(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } - dialog.dismiss(); - showRelativeMouseMovementConfig1(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } + static void showGyroscopeMouseMovementConfig(final MainActivity p) { + if (!Globals.MoveMouseWithGyroscope) { + showRelativeMouseMovementConfig(p); + return; + } - static void showRelativeMouseMovementConfig1(final MainActivity p) - { - final CharSequence[] items = { p.getResources().getString(R.string.none), - p.getResources().getString(R.string.accel_slow), - p.getResources().getString(R.string.accel_medium), - p.getResources().getString(R.string.accel_fast) }; + final CharSequence[] items = {p.getResources().getString(R.string.accel_veryslow), + p.getResources().getString(R.string.accel_slow), + p.getResources().getString(R.string.accel_medium), + p.getResources().getString(R.string.accel_fast), + p.getResources().getString(R.string.accel_veryfast)}; - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.mouse_relative_accel); - builder.setSingleChoiceItems(items, Globals.RelativeMouseMovementAccel, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.RelativeMouseMovementAccel = item; + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.mouse_gyroscope_mouse_sensitivity); + builder.setSingleChoiceItems(items, Globals.MoveMouseWithGyroscopeSpeed, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.MoveMouseWithGyroscopeSpeed = item; - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + dialog.dismiss(); + showRelativeMouseMovementConfig(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } - static class JoystickMouseConfig extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.mouse_joystickmousespeed); - } - boolean enabled() - { - return Globals.MoveMouseWithJoystick; - }; - void run (final MainActivity p) - { - final CharSequence[] items = { p.getResources().getString(R.string.accel_slow), - p.getResources().getString(R.string.accel_medium), - p.getResources().getString(R.string.accel_fast) }; + static void showRelativeMouseMovementConfig(final MainActivity p) { + if (!Globals.RelativeMouseMovement) { + goBack(p); + return; + } - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.mouse_joystickmousespeed); - builder.setSingleChoiceItems(items, Globals.MoveMouseWithJoystickSpeed, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.MoveMouseWithJoystickSpeed = item; + final CharSequence[] items = {p.getResources().getString(R.string.accel_veryslow), + p.getResources().getString(R.string.accel_slow), + p.getResources().getString(R.string.accel_medium), + p.getResources().getString(R.string.accel_fast), + p.getResources().getString(R.string.accel_veryfast)}; - dialog.dismiss(); - showJoystickMouseAccelConfig(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.mouse_relative_speed); + builder.setSingleChoiceItems(items, Globals.RelativeMouseMovementSpeed, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.RelativeMouseMovementSpeed = item; - static void showJoystickMouseAccelConfig(final MainActivity p) - { - final CharSequence[] items = { p.getResources().getString(R.string.none), - p.getResources().getString(R.string.accel_slow), - p.getResources().getString(R.string.accel_medium), - p.getResources().getString(R.string.accel_fast) }; + dialog.dismiss(); + showRelativeMouseMovementConfig1(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(R.string.mouse_joystickmouseaccel); - builder.setSingleChoiceItems(items, Globals.MoveMouseWithJoystickAccel, new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - Globals.MoveMouseWithJoystickAccel = item; + static void showRelativeMouseMovementConfig1(final MainActivity p) { + final CharSequence[] items = {p.getResources().getString(R.string.none), + p.getResources().getString(R.string.accel_slow), + p.getResources().getString(R.string.accel_medium), + p.getResources().getString(R.string.accel_fast)}; - dialog.dismiss(); - goBack(p); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - goBack(p); - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.mouse_relative_accel); + builder.setSingleChoiceItems(items, Globals.RelativeMouseMovementAccel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.RelativeMouseMovementAccel = item; - static class TouchPressureMeasurementTool extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.measurepressure); - } - boolean enabled() - { - return Globals.RightClickMethod == Mouse.RIGHT_CLICK_WITH_PRESSURE || - Globals.LeftClickMethod == Mouse.LEFT_CLICK_WITH_PRESSURE; - }; - void run (final MainActivity p) - { - p.setText(p.getResources().getString(R.string.measurepressure_touchplease)); - p.getVideoLayout().setOnTouchListener(new TouchMeasurementTool(p)); - } + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } - public static class TouchMeasurementTool implements View.OnTouchListener - { - MainActivity p; - ArrayList force = new ArrayList(); - ArrayList radius = new ArrayList(); - static final int maxEventAmount = 100; - - public TouchMeasurementTool(MainActivity _p) - { - p = _p; - } + static class JoystickMouseConfig extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.mouse_joystickmousespeed); + } - @Override - public boolean onTouch(View v, MotionEvent ev) - { - force.add(new Integer((int)(ev.getPressure() * 1000.0))); - radius.add(new Integer((int)(ev.getSize() * 1000.0))); - p.setText(p.getResources().getString(R.string.measurepressure_response, force.get(force.size()-1), radius.get(radius.size()-1))); - try { - Thread.sleep(10L); - } catch (InterruptedException e) { } - - if( force.size() >= maxEventAmount ) - { - p.getVideoLayout().setOnTouchListener(null); - Globals.ClickScreenPressure = getAverageForce(); - Globals.ClickScreenTouchspotSize = getAverageRadius(); - Log.i("SDL", "SDL: measured average force " + Globals.ClickScreenPressure + " radius " + Globals.ClickScreenTouchspotSize); - goBack(p); - } - return true; - } + boolean enabled() { + return Globals.MoveMouseWithJoystick; + } - int getAverageForce() - { - int avg = 0; - for(Integer f: force) - { - avg += f; - } - return avg / force.size(); - } - int getAverageRadius() - { - int avg = 0; - for(Integer r: radius) - { - avg += r; - } - return avg / radius.size(); - } - } - } - - static class CalibrateTouchscreenMenu extends Menu - { - String title(final MainActivity p) - { - return p.getResources().getString(R.string.calibrate_touchscreen); - } - //boolean enabled() { return true; }; - void run (final MainActivity p) - { - p.setText(p.getResources().getString(R.string.calibrate_touchscreen_touch)); - Globals.TouchscreenCalibration[0] = 0; - Globals.TouchscreenCalibration[1] = 0; - Globals.TouchscreenCalibration[2] = 0; - Globals.TouchscreenCalibration[3] = 0; - ScreenEdgesCalibrationTool tool = new ScreenEdgesCalibrationTool(p); - p.getVideoLayout().setOnTouchListener(tool); - p.getVideoLayout().setOnKeyListener(tool); - } + ; - static class ScreenEdgesCalibrationTool implements View.OnTouchListener, View.OnKeyListener - { - MainActivity p; - ImageView img; - Bitmap bmp; - - public ScreenEdgesCalibrationTool(MainActivity _p) - { - p = _p; - img = new ImageView(p); - img.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - img.setScaleType(ImageView.ScaleType.MATRIX); - bmp = BitmapFactory.decodeResource( p.getResources(), R.drawable.calibrate ); - img.setImageBitmap(bmp); - Matrix m = new Matrix(); - RectF src = new RectF(0, 0, bmp.getWidth(), bmp.getHeight()); - RectF dst = new RectF(Globals.TouchscreenCalibration[0], Globals.TouchscreenCalibration[1], - Globals.TouchscreenCalibration[2], Globals.TouchscreenCalibration[3]); - m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); - img.setImageMatrix(m); - p.getVideoLayout().addView(img); - } + void run(final MainActivity p) { + final CharSequence[] items = {p.getResources().getString(R.string.accel_slow), + p.getResources().getString(R.string.accel_medium), + p.getResources().getString(R.string.accel_fast)}; - @Override - public boolean onTouch(View v, MotionEvent ev) - { - if( Globals.TouchscreenCalibration[0] == Globals.TouchscreenCalibration[1] && - Globals.TouchscreenCalibration[1] == Globals.TouchscreenCalibration[2] && - Globals.TouchscreenCalibration[2] == Globals.TouchscreenCalibration[3] ) - { - Globals.TouchscreenCalibration[0] = (int)ev.getX(); - Globals.TouchscreenCalibration[1] = (int)ev.getY(); - Globals.TouchscreenCalibration[2] = (int)ev.getX(); - Globals.TouchscreenCalibration[3] = (int)ev.getY(); - } - if( ev.getX() < Globals.TouchscreenCalibration[0] ) - Globals.TouchscreenCalibration[0] = (int)ev.getX(); - if( ev.getY() < Globals.TouchscreenCalibration[1] ) - Globals.TouchscreenCalibration[1] = (int)ev.getY(); - if( ev.getX() > Globals.TouchscreenCalibration[2] ) - Globals.TouchscreenCalibration[2] = (int)ev.getX(); - if( ev.getY() > Globals.TouchscreenCalibration[3] ) - Globals.TouchscreenCalibration[3] = (int)ev.getY(); - Matrix m = new Matrix(); - RectF src = new RectF(0, 0, bmp.getWidth(), bmp.getHeight()); - RectF dst = new RectF(Globals.TouchscreenCalibration[0], Globals.TouchscreenCalibration[1], - Globals.TouchscreenCalibration[2], Globals.TouchscreenCalibration[3]); - m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); - img.setImageMatrix(m); - return true; - } + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.mouse_joystickmousespeed); + builder.setSingleChoiceItems(items, Globals.MoveMouseWithJoystickSpeed, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.MoveMouseWithJoystickSpeed = item; - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) - { - p.getVideoLayout().setOnTouchListener(null); - p.getVideoLayout().setOnKeyListener(null); - p.getVideoLayout().removeView(img); - goBack(p); - return true; - } - } - } + dialog.dismiss(); + showJoystickMouseAccelConfig(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + + static void showJoystickMouseAccelConfig(final MainActivity p) { + final CharSequence[] items = {p.getResources().getString(R.string.none), + p.getResources().getString(R.string.accel_slow), + p.getResources().getString(R.string.accel_medium), + p.getResources().getString(R.string.accel_fast)}; + + AlertDialog.Builder builder = new AlertDialog.Builder(p); + builder.setTitle(R.string.mouse_joystickmouseaccel); + builder.setSingleChoiceItems(items, Globals.MoveMouseWithJoystickAccel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Globals.MoveMouseWithJoystickAccel = item; + + dialog.dismiss(); + goBack(p); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + goBack(p); + } + }); + AlertDialog alert = builder.create(); + alert.setOwnerActivity(p); + alert.show(); + } + } + + static class TouchPressureMeasurementTool extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.measurepressure); + } + + boolean enabled() { + return Globals.RightClickMethod == Mouse.RIGHT_CLICK_WITH_PRESSURE || + Globals.LeftClickMethod == Mouse.LEFT_CLICK_WITH_PRESSURE; + } + + ; + + void run(final MainActivity p) { + p.setText(p.getResources().getString(R.string.measurepressure_touchplease)); + p.getVideoLayout().setOnTouchListener(new TouchMeasurementTool(p)); + } + + public static class TouchMeasurementTool implements View.OnTouchListener { + MainActivity p; + ArrayList force = new ArrayList(); + ArrayList radius = new ArrayList(); + static final int maxEventAmount = 100; + + public TouchMeasurementTool(MainActivity _p) { + p = _p; + } + + @Override + public boolean onTouch(View v, MotionEvent ev) { + force.add(new Integer((int) (ev.getPressure() * 1000.0))); + radius.add(new Integer((int) (ev.getSize() * 1000.0))); + p.setText(p.getResources().getString(R.string.measurepressure_response, force.get(force.size() - 1), radius.get(radius.size() - 1))); + try { + Thread.sleep(10L); + } catch (InterruptedException e) { + } + + if (force.size() >= maxEventAmount) { + p.getVideoLayout().setOnTouchListener(null); + Globals.ClickScreenPressure = getAverageForce(); + Globals.ClickScreenTouchspotSize = getAverageRadius(); + Log.i("SDL", "SDL: measured average force " + Globals.ClickScreenPressure + " radius " + Globals.ClickScreenTouchspotSize); + goBack(p); + } + return true; + } + + int getAverageForce() { + int avg = 0; + for (Integer f : force) { + avg += f; + } + return avg / force.size(); + } + + int getAverageRadius() { + int avg = 0; + for (Integer r : radius) { + avg += r; + } + return avg / radius.size(); + } + } + } + + static class CalibrateTouchscreenMenu extends Menu { + String title(final MainActivity p) { + return p.getResources().getString(R.string.calibrate_touchscreen); + } + + //boolean enabled() { return true; }; + void run(final MainActivity p) { + p.setText(p.getResources().getString(R.string.calibrate_touchscreen_touch)); + Globals.TouchscreenCalibration[0] = 0; + Globals.TouchscreenCalibration[1] = 0; + Globals.TouchscreenCalibration[2] = 0; + Globals.TouchscreenCalibration[3] = 0; + ScreenEdgesCalibrationTool tool = new ScreenEdgesCalibrationTool(p); + p.getVideoLayout().setOnTouchListener(tool); + p.getVideoLayout().setOnKeyListener(tool); + } + + static class ScreenEdgesCalibrationTool implements View.OnTouchListener, View.OnKeyListener { + MainActivity p; + ImageView img; + Bitmap bmp; + + public ScreenEdgesCalibrationTool(MainActivity _p) { + p = _p; + img = new ImageView(p); + img.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); + img.setScaleType(ImageView.ScaleType.MATRIX); + bmp = BitmapFactory.decodeResource(p.getResources(), R.drawable.calibrate); + img.setImageBitmap(bmp); + Matrix m = new Matrix(); + RectF src = new RectF(0, 0, bmp.getWidth(), bmp.getHeight()); + RectF dst = new RectF(Globals.TouchscreenCalibration[0], Globals.TouchscreenCalibration[1], + Globals.TouchscreenCalibration[2], Globals.TouchscreenCalibration[3]); + m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); + img.setImageMatrix(m); + p.getVideoLayout().addView(img); + } + + @Override + public boolean onTouch(View v, MotionEvent ev) { + if (Globals.TouchscreenCalibration[0] == Globals.TouchscreenCalibration[1] && + Globals.TouchscreenCalibration[1] == Globals.TouchscreenCalibration[2] && + Globals.TouchscreenCalibration[2] == Globals.TouchscreenCalibration[3]) { + Globals.TouchscreenCalibration[0] = (int) ev.getX(); + Globals.TouchscreenCalibration[1] = (int) ev.getY(); + Globals.TouchscreenCalibration[2] = (int) ev.getX(); + Globals.TouchscreenCalibration[3] = (int) ev.getY(); + } + if (ev.getX() < Globals.TouchscreenCalibration[0]) + Globals.TouchscreenCalibration[0] = (int) ev.getX(); + if (ev.getY() < Globals.TouchscreenCalibration[1]) + Globals.TouchscreenCalibration[1] = (int) ev.getY(); + if (ev.getX() > Globals.TouchscreenCalibration[2]) + Globals.TouchscreenCalibration[2] = (int) ev.getX(); + if (ev.getY() > Globals.TouchscreenCalibration[3]) + Globals.TouchscreenCalibration[3] = (int) ev.getY(); + Matrix m = new Matrix(); + RectF src = new RectF(0, 0, bmp.getWidth(), bmp.getHeight()); + RectF dst = new RectF(Globals.TouchscreenCalibration[0], Globals.TouchscreenCalibration[1], + Globals.TouchscreenCalibration[2], Globals.TouchscreenCalibration[3]); + m.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); + img.setImageMatrix(m); + return true; + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + p.getVideoLayout().setOnTouchListener(null); + p.getVideoLayout().setOnKeyListener(null); + p.getVideoLayout().removeView(img); + goBack(p); + return true; + } + } + } } diff --git a/Xorg/src/main/java/io/neoterm/Video.java b/Xorg/src/main/java/io/neoterm/Video.java index 8fcea41..1b68437 100644 --- a/Xorg/src/main/java/io/neoterm/Video.java +++ b/Xorg/src/main/java/io/neoterm/Video.java @@ -22,212 +22,185 @@ freely, subject to the following restrictions: package io.neoterm; -import javax.microedition.khronos.opengles.GL10; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; - -import java.lang.reflect.Method; - +import android.content.Context; +import android.content.Intent; +import android.hardware.input.InputManager; +import android.net.Uri; import android.os.Build; import android.util.DisplayMetrics; import android.util.Log; -import android.content.Context; -import android.view.MotionEvent; -import android.view.KeyEvent; -import android.view.InputDevice; +import android.view.*; import android.widget.Toast; -import android.content.Intent; -import android.view.View; -import android.view.Display; -import android.net.Uri; -import android.hardware.input.InputManager; - import io.neoterm.xorg.NeoXorgViewClient; +import javax.microedition.khronos.egl.*; +import javax.microedition.khronos.opengles.GL10; +import java.lang.reflect.Method; -class Mouse -{ - public static final int LEFT_CLICK_NORMAL = 0; - public static final int LEFT_CLICK_NEAR_CURSOR = 1; - public static final int LEFT_CLICK_WITH_MULTITOUCH = 2; - public static final int LEFT_CLICK_WITH_PRESSURE = 3; - public static final int LEFT_CLICK_WITH_KEY = 4; - public static final int LEFT_CLICK_WITH_TIMEOUT = 5; - public static final int LEFT_CLICK_WITH_TAP = 6; - public static final int LEFT_CLICK_WITH_TAP_OR_TIMEOUT = 7; - - public static final int RIGHT_CLICK_NONE = 0; - public static final int RIGHT_CLICK_WITH_MULTITOUCH = 1; - public static final int RIGHT_CLICK_WITH_PRESSURE = 2; - public static final int RIGHT_CLICK_WITH_KEY = 3; - public static final int RIGHT_CLICK_WITH_TIMEOUT = 4; - public static final int SDL_FINGER_DOWN = 0; - public static final int SDL_FINGER_UP = 1; - public static final int SDL_FINGER_MOVE = 2; - public static final int SDL_FINGER_HOVER = 3; +class Mouse { + public static final int LEFT_CLICK_NORMAL = 0; + public static final int LEFT_CLICK_NEAR_CURSOR = 1; + public static final int LEFT_CLICK_WITH_MULTITOUCH = 2; + public static final int LEFT_CLICK_WITH_PRESSURE = 3; + public static final int LEFT_CLICK_WITH_KEY = 4; + public static final int LEFT_CLICK_WITH_TIMEOUT = 5; + public static final int LEFT_CLICK_WITH_TAP = 6; + public static final int LEFT_CLICK_WITH_TAP_OR_TIMEOUT = 7; - public static final int ZOOM_NONE = 0; - public static final int ZOOM_MAGNIFIER = 1; + public static final int RIGHT_CLICK_NONE = 0; + public static final int RIGHT_CLICK_WITH_MULTITOUCH = 1; + public static final int RIGHT_CLICK_WITH_PRESSURE = 2; + public static final int RIGHT_CLICK_WITH_KEY = 3; + public static final int RIGHT_CLICK_WITH_TIMEOUT = 4; - public static final int MOUSE_HW_INPUT_FINGER = 0; - public static final int MOUSE_HW_INPUT_STYLUS = 1; - public static final int MOUSE_HW_INPUT_MOUSE = 2; + public static final int SDL_FINGER_DOWN = 0; + public static final int SDL_FINGER_UP = 1; + public static final int SDL_FINGER_MOVE = 2; + public static final int SDL_FINGER_HOVER = 3; - public static final int MAX_HOVER_DISTANCE = 1024; - public static final int HOVER_REDRAW_SCREEN = 1024 * 10; - public static final float MAX_PRESSURE = 1024.0f; + public static final int ZOOM_NONE = 0; + public static final int ZOOM_MAGNIFIER = 1; + + public static final int MOUSE_HW_INPUT_FINGER = 0; + public static final int MOUSE_HW_INPUT_STYLUS = 1; + public static final int MOUSE_HW_INPUT_MOUSE = 2; + + public static final int MAX_HOVER_DISTANCE = 1024; + public static final int HOVER_REDRAW_SCREEN = 1024 * 10; + public static final float MAX_PRESSURE = 1024.0f; } -abstract class DifferentTouchInput -{ - public abstract void process(final MotionEvent event); - public abstract void processGenericEvent(final MotionEvent event); +abstract class DifferentTouchInput { + public abstract void process(final MotionEvent event); - public static int ExternalMouseDetected = Mouse.MOUSE_HW_INPUT_FINGER; + public abstract void processGenericEvent(final MotionEvent event); - public static DifferentTouchInput touchInput = getInstance(); + public static int ExternalMouseDetected = Mouse.MOUSE_HW_INPUT_FINGER; - public static DifferentTouchInput getInstance() - { - boolean multiTouchAvailable1 = false; - boolean multiTouchAvailable2 = false; - // Not checking for getX(int), getY(int) etc 'cause I'm lazy - Method methods [] = MotionEvent.class.getDeclaredMethods(); - for(Method m: methods) - { - if( m.getName().equals("getPointerCount") ) - multiTouchAvailable1 = true; - if( m.getName().equals("getPointerId") ) - multiTouchAvailable2 = true; - } - try { - Log.i("SDL", "Device: " + Build.DEVICE); - Log.i("SDL", "Device name: " + Build.DISPLAY); - Log.i("SDL", "Device model: " + Build.MODEL); - Log.i("SDL", "Device board: " + Build.BOARD); - if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH ) - { - //return IcsTouchInput.Holder.sInstance; - return AutoDetectTouchInput.Holder.sInstance; - } - if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD ) - return GingerbreadTouchInput.Holder.sInstance; - if (multiTouchAvailable1 && multiTouchAvailable2) - return MultiTouchInput.Holder.sInstance; - else - return SingleTouchInput.Holder.sInstance; - } catch( Exception e ) { - try { - if (multiTouchAvailable1 && multiTouchAvailable2) - return MultiTouchInput.Holder.sInstance; - else - return SingleTouchInput.Holder.sInstance; - } catch( Exception ee ) { - return SingleTouchInput.Holder.sInstance; - } - } - } + public static DifferentTouchInput touchInput = getInstance(); - private static class SingleTouchInput extends DifferentTouchInput - { - private static class Holder - { - private static final SingleTouchInput sInstance = new SingleTouchInput(); - } - @Override - public void processGenericEvent(final MotionEvent event) - { - process(event); - } - public void process(final MotionEvent event) - { - int action = -1; - if( event.getAction() == MotionEvent.ACTION_DOWN ) - action = Mouse.SDL_FINGER_DOWN; - if( event.getAction() == MotionEvent.ACTION_UP ) - action = Mouse.SDL_FINGER_UP; - if( event.getAction() == MotionEvent.ACTION_MOVE ) - action = Mouse.SDL_FINGER_MOVE; - if ( action >= 0 ) - DemoGLSurfaceView.nativeMotionEvent( (int)event.getX(), (int)event.getY(), action, 0, - (int)(event.getPressure() * Mouse.MAX_PRESSURE), - (int)(event.getSize() * Mouse.MAX_PRESSURE) ); - } - } - private static class MultiTouchInput extends DifferentTouchInput - { - public static final int TOUCH_EVENTS_MAX = 16; // Max multitouch pointers + public static DifferentTouchInput getInstance() { + boolean multiTouchAvailable1 = false; + boolean multiTouchAvailable2 = false; + // Not checking for getX(int), getY(int) etc 'cause I'm lazy + Method methods[] = MotionEvent.class.getDeclaredMethods(); + for (Method m : methods) { + if (m.getName().equals("getPointerCount")) + multiTouchAvailable1 = true; + if (m.getName().equals("getPointerId")) + multiTouchAvailable2 = true; + } + try { + Log.i("SDL", "Device: " + Build.DEVICE); + Log.i("SDL", "Device name: " + Build.DISPLAY); + Log.i("SDL", "Device model: " + Build.MODEL); + Log.i("SDL", "Device board: " + Build.BOARD); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + //return IcsTouchInput.Holder.sInstance; + return AutoDetectTouchInput.Holder.sInstance; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) + return GingerbreadTouchInput.Holder.sInstance; + if (multiTouchAvailable1 && multiTouchAvailable2) + return MultiTouchInput.Holder.sInstance; + else + return SingleTouchInput.Holder.sInstance; + } catch (Exception e) { + try { + if (multiTouchAvailable1 && multiTouchAvailable2) + return MultiTouchInput.Holder.sInstance; + else + return SingleTouchInput.Holder.sInstance; + } catch (Exception ee) { + return SingleTouchInput.Holder.sInstance; + } + } + } - private class touchEvent - { - public boolean down = false; - public int x = 0; - public int y = 0; - public int pressure = 0; - public int size = 0; - } - - protected touchEvent touchEvents[]; - - MultiTouchInput() - { - touchEvents = new touchEvent[TOUCH_EVENTS_MAX]; - for( int i = 0; i < TOUCH_EVENTS_MAX; i++ ) - touchEvents[i] = new touchEvent(); - } - - private static class Holder - { - private static final MultiTouchInput sInstance = new MultiTouchInput(); - } + private static class SingleTouchInput extends DifferentTouchInput { + private static class Holder { + private static final SingleTouchInput sInstance = new SingleTouchInput(); + } - public void processGenericEvent(final MotionEvent event) - { - process(event); - } - public void process(final MotionEvent event) - { - int action = -1; + @Override + public void processGenericEvent(final MotionEvent event) { + process(event); + } - //Log.i("SDL", "Got motion event, type " + (int)(event.getAction()) + " X " + (int)event.getX() + " Y " + (int)event.getY()); - if( (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP || - (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_CANCEL ) - { - action = Mouse.SDL_FINGER_UP; - for( int i = 0; i < TOUCH_EVENTS_MAX; i++ ) - { - if( touchEvents[i].down ) - { - touchEvents[i].down = false; - DemoGLSurfaceView.nativeMotionEvent( touchEvents[i].x, touchEvents[i].y, action, i, touchEvents[i].pressure, touchEvents[i].size ); - } - } - } - if( (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN ) - { - action = Mouse.SDL_FINGER_DOWN; - for( int i = 0; i < event.getPointerCount(); i++ ) - { - int id = event.getPointerId(i); - if( id >= TOUCH_EVENTS_MAX ) - id = TOUCH_EVENTS_MAX - 1; - touchEvents[id].down = true; - touchEvents[id].x = (int)event.getX(i); - touchEvents[id].y = (int)event.getY(i); - touchEvents[id].pressure = (int)(event.getPressure(i) * Mouse.MAX_PRESSURE); - touchEvents[id].size = (int)(event.getSize(i) * Mouse.MAX_PRESSURE); - DemoGLSurfaceView.nativeMotionEvent( touchEvents[id].x, touchEvents[id].y, action, id, touchEvents[id].pressure, touchEvents[id].size ); - } - } - if( (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_MOVE || - (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN || - (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP ) - { + public void process(final MotionEvent event) { + int action = -1; + if (event.getAction() == MotionEvent.ACTION_DOWN) + action = Mouse.SDL_FINGER_DOWN; + if (event.getAction() == MotionEvent.ACTION_UP) + action = Mouse.SDL_FINGER_UP; + if (event.getAction() == MotionEvent.ACTION_MOVE) + action = Mouse.SDL_FINGER_MOVE; + if (action >= 0) + DemoGLSurfaceView.nativeMotionEvent((int) event.getX(), (int) event.getY(), action, 0, + (int) (event.getPressure() * Mouse.MAX_PRESSURE), + (int) (event.getSize() * Mouse.MAX_PRESSURE)); + } + } + + private static class MultiTouchInput extends DifferentTouchInput { + public static final int TOUCH_EVENTS_MAX = 16; // Max multitouch pointers + + private class touchEvent { + public boolean down = false; + public int x = 0; + public int y = 0; + public int pressure = 0; + public int size = 0; + } + + protected touchEvent touchEvents[]; + + MultiTouchInput() { + touchEvents = new touchEvent[TOUCH_EVENTS_MAX]; + for (int i = 0; i < TOUCH_EVENTS_MAX; i++) + touchEvents[i] = new touchEvent(); + } + + private static class Holder { + private static final MultiTouchInput sInstance = new MultiTouchInput(); + } + + public void processGenericEvent(final MotionEvent event) { + process(event); + } + + public void process(final MotionEvent event) { + int action = -1; + + //Log.i("SDL", "Got motion event, type " + (int)(event.getAction()) + " X " + (int)event.getX() + " Y " + (int)event.getY()); + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP || + (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_CANCEL) { + action = Mouse.SDL_FINGER_UP; + for (int i = 0; i < TOUCH_EVENTS_MAX; i++) { + if (touchEvents[i].down) { + touchEvents[i].down = false; + DemoGLSurfaceView.nativeMotionEvent(touchEvents[i].x, touchEvents[i].y, action, i, touchEvents[i].pressure, touchEvents[i].size); + } + } + } + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + action = Mouse.SDL_FINGER_DOWN; + for (int i = 0; i < event.getPointerCount(); i++) { + int id = event.getPointerId(i); + if (id >= TOUCH_EVENTS_MAX) + id = TOUCH_EVENTS_MAX - 1; + touchEvents[id].down = true; + touchEvents[id].x = (int) event.getX(i); + touchEvents[id].y = (int) event.getY(i); + touchEvents[id].pressure = (int) (event.getPressure(i) * Mouse.MAX_PRESSURE); + touchEvents[id].size = (int) (event.getSize(i) * Mouse.MAX_PRESSURE); + DemoGLSurfaceView.nativeMotionEvent(touchEvents[id].x, touchEvents[id].y, action, id, touchEvents[id].pressure, touchEvents[id].size); + } + } + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_MOVE || + (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN || + (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) { /* String s = "MOVE: ptrs " + event.getPointerCount(); for( int i = 0 ; i < event.getPointerCount(); i++ ) @@ -236,906 +209,827 @@ abstract class DifferentTouchInput } Log.i("SDL", s); */ - int pointerReleased = -1; - if( (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP ) - pointerReleased = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + int pointerReleased = -1; + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) + pointerReleased = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; - for( int id = 0; id < TOUCH_EVENTS_MAX; id++ ) - { - int ii; - for( ii = 0; ii < event.getPointerCount(); ii++ ) - { - if( id == event.getPointerId(ii) ) - break; - } - if( ii >= event.getPointerCount() ) - { - // Up event - if( touchEvents[id].down ) - { - action = Mouse.SDL_FINGER_UP; - touchEvents[id].down = false; - DemoGLSurfaceView.nativeMotionEvent( touchEvents[id].x, touchEvents[id].y, action, id, touchEvents[id].pressure, touchEvents[id].size ); - } - } - else - { - if( pointerReleased == id && touchEvents[pointerReleased].down ) - { - action = Mouse.SDL_FINGER_UP; - touchEvents[id].down = false; - } - else if( touchEvents[id].down ) - { - action = Mouse.SDL_FINGER_MOVE; - } - else - { - action = Mouse.SDL_FINGER_DOWN; - touchEvents[id].down = true; - } - touchEvents[id].x = (int)event.getX(ii); - touchEvents[id].y = (int)event.getY(ii); - touchEvents[id].pressure = (int)(event.getPressure(ii) * Mouse.MAX_PRESSURE); - touchEvents[id].size = (int)(event.getSize(ii) * Mouse.MAX_PRESSURE); - DemoGLSurfaceView.nativeMotionEvent( touchEvents[id].x, touchEvents[id].y, action, id, touchEvents[id].pressure, touchEvents[id].size ); - } - } - } - } - } - private static class GingerbreadTouchInput extends MultiTouchInput - { - private static class Holder - { - private static final GingerbreadTouchInput sInstance = new GingerbreadTouchInput(); - } + for (int id = 0; id < TOUCH_EVENTS_MAX; id++) { + int ii; + for (ii = 0; ii < event.getPointerCount(); ii++) { + if (id == event.getPointerId(ii)) + break; + } + if (ii >= event.getPointerCount()) { + // Up event + if (touchEvents[id].down) { + action = Mouse.SDL_FINGER_UP; + touchEvents[id].down = false; + DemoGLSurfaceView.nativeMotionEvent(touchEvents[id].x, touchEvents[id].y, action, id, touchEvents[id].pressure, touchEvents[id].size); + } + } else { + if (pointerReleased == id && touchEvents[pointerReleased].down) { + action = Mouse.SDL_FINGER_UP; + touchEvents[id].down = false; + } else if (touchEvents[id].down) { + action = Mouse.SDL_FINGER_MOVE; + } else { + action = Mouse.SDL_FINGER_DOWN; + touchEvents[id].down = true; + } + touchEvents[id].x = (int) event.getX(ii); + touchEvents[id].y = (int) event.getY(ii); + touchEvents[id].pressure = (int) (event.getPressure(ii) * Mouse.MAX_PRESSURE); + touchEvents[id].size = (int) (event.getSize(ii) * Mouse.MAX_PRESSURE); + DemoGLSurfaceView.nativeMotionEvent(touchEvents[id].x, touchEvents[id].y, action, id, touchEvents[id].pressure, touchEvents[id].size); + } + } + } + } + } - GingerbreadTouchInput() - { - super(); - } - public void process(final MotionEvent event) - { - int hwMouseEvent = ((event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE || Globals.ForceHardwareMouse) ? Mouse.MOUSE_HW_INPUT_MOUSE : - ((event.getSource() & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) ? Mouse.MOUSE_HW_INPUT_STYLUS : - Mouse.MOUSE_HW_INPUT_FINGER; - if( ExternalMouseDetected != hwMouseEvent ) - { - ExternalMouseDetected = hwMouseEvent; - DemoGLSurfaceView.nativeHardwareMouseDetected(hwMouseEvent); - } - super.process(event); - if( !Globals.FingerHover && ExternalMouseDetected == Mouse.MOUSE_HW_INPUT_FINGER ) - return; // Finger hover disabled in settings - if( (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_HOVER_MOVE ) // Support bluetooth/USB mouse - available since Android 3.1 - { - int action; - // TODO: it is possible that multiple pointers return that event, but we're handling only pointer #0 - if( touchEvents[0].down ) - action = Mouse.SDL_FINGER_UP; - else - action = Mouse.SDL_FINGER_HOVER; - touchEvents[0].down = false; - touchEvents[0].x = (int)event.getX(); - touchEvents[0].y = (int)event.getY(); - touchEvents[0].pressure = Mouse.MAX_HOVER_DISTANCE; - touchEvents[0].size = 0; - //if( event.getAxisValue(MotionEvent.AXIS_DISTANCE) != 0.0f ) - InputDevice device = InputDevice.getDevice(event.getDeviceId()); - if( device != null && device.getMotionRange(MotionEvent.AXIS_DISTANCE) != null && - device.getMotionRange(MotionEvent.AXIS_DISTANCE).getRange() > 0.0f ) - touchEvents[0].pressure = (int)((event.getAxisValue(MotionEvent.AXIS_DISTANCE) - - device.getMotionRange(MotionEvent.AXIS_DISTANCE).getMin()) * Mouse.MAX_PRESSURE / device.getMotionRange(MotionEvent.AXIS_DISTANCE).getRange()); - DemoGLSurfaceView.nativeMotionEvent( touchEvents[0].x, touchEvents[0].y, action, 0, touchEvents[0].pressure, touchEvents[0].size ); - } - if( (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_HOVER_EXIT ) // Update screen for finger hover - { - touchEvents[0].pressure = Mouse.HOVER_REDRAW_SCREEN; - touchEvents[0].size = 0; - DemoGLSurfaceView.nativeMotionEvent( touchEvents[0].x, touchEvents[0].y, Mouse.SDL_FINGER_HOVER, 0, touchEvents[0].pressure, touchEvents[0].size ); - } - } - public void processGenericEvent(final MotionEvent event) - { - process(event); - } - } - private static class IcsTouchInput extends GingerbreadTouchInput - { - private static class Holder - { - private static final IcsTouchInput sInstance = new IcsTouchInput(); - } - private int buttonState = 0; - public void process(final MotionEvent event) - { - //Log.i("SDL", "Got motion event, type " + (int)(event.getAction()) + " X " + (int)event.getX() + " Y " + (int)event.getY() + " buttons " + buttonState + " source " + event.getSource()); - int buttonStateNew = event.getButtonState(); - if( buttonStateNew != buttonState ) - { - for( int i = 1; i <= MotionEvent.BUTTON_FORWARD; i *= 2 ) - { - if( (buttonStateNew & i) != (buttonState & i) ) - DemoGLSurfaceView.nativeMouseButtonsPressed(i, ((buttonStateNew & i) == 0) ? 0 : 1); - } - if( (buttonStateNew & MotionEvent.BUTTON_STYLUS_PRIMARY) != (buttonState & MotionEvent.BUTTON_STYLUS_PRIMARY) ) - DemoGLSurfaceView.nativeMouseButtonsPressed(2, ((buttonStateNew & MotionEvent.BUTTON_STYLUS_PRIMARY) == 0) ? 0 : 1); - if( (buttonStateNew & MotionEvent.BUTTON_STYLUS_SECONDARY) != (buttonState & MotionEvent.BUTTON_STYLUS_SECONDARY) ) - DemoGLSurfaceView.nativeMouseButtonsPressed(4, ((buttonStateNew & MotionEvent.BUTTON_STYLUS_SECONDARY) == 0) ? 0 : 1); - buttonState = buttonStateNew; - } - super.process(event); - } - public void processGenericEvent(final MotionEvent event) - { - // Joysticks are supported since Honeycomb, but I don't care about it, because very few devices have it - if( (event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK ) - { - // event.getAxisValue(AXIS_HAT_X) and event.getAxisValue(AXIS_HAT_Y) are joystick arrow keys, on Nvidia Shield and some other joysticks - DemoGLSurfaceView.nativeGamepadAnalogJoystickInput( - event.getAxisValue(MotionEvent.AXIS_X), event.getAxisValue(MotionEvent.AXIS_Y), - event.getAxisValue(MotionEvent.AXIS_Z), event.getAxisValue(MotionEvent.AXIS_RZ), - event.getAxisValue(MotionEvent.AXIS_LTRIGGER), event.getAxisValue(MotionEvent.AXIS_RTRIGGER), - event.getAxisValue(MotionEvent.AXIS_HAT_X), event.getAxisValue(MotionEvent.AXIS_HAT_Y), - processGamepadDeviceId(event.getDevice()) ); - return; - } - // Process mousewheel - if( event.getAction() == MotionEvent.ACTION_SCROLL ) - { - int scrollX = Math.round(event.getAxisValue(MotionEvent.AXIS_HSCROLL)); - int scrollY = Math.round(event.getAxisValue(MotionEvent.AXIS_VSCROLL)); - DemoGLSurfaceView.nativeMouseWheel(scrollX, scrollY); - return; - } - super.processGenericEvent(event); - } - } - private static class IcsTouchInputWithHistory extends IcsTouchInput - { - private static class Holder - { - private static final IcsTouchInputWithHistory sInstance = new IcsTouchInputWithHistory(); - } - public void process(final MotionEvent event) - { - int ptr = 0; // Process only one touch event, because that's typically a pen/mouse - for( ptr = 0; ptr < TOUCH_EVENTS_MAX; ptr++ ) - { - if( touchEvents[ptr].down ) - break; - } - if( ptr >= TOUCH_EVENTS_MAX ) - ptr = 0; - //Log.i("SDL", "Got motion event, getHistorySize " + (int)(event.getHistorySize()) + " ptr " + ptr); + private static class GingerbreadTouchInput extends MultiTouchInput { + private static class Holder { + private static final GingerbreadTouchInput sInstance = new GingerbreadTouchInput(); + } - for( int i = 0; i < event.getHistorySize(); i++ ) - { - DemoGLSurfaceView.nativeMotionEvent( (int)event.getHistoricalX(i), (int)event.getHistoricalY(i), - Mouse.SDL_FINGER_MOVE, ptr, (int)( event.getHistoricalPressure(i) * Mouse.MAX_PRESSURE ), (int)( event.getHistoricalSize(i) * Mouse.MAX_PRESSURE ) ); - } - super.process(event); - } - } - private static class CrappyMtkTabletWithBrokenTouchDrivers extends IcsTouchInput - { - private static class Holder - { - private static final CrappyMtkTabletWithBrokenTouchDrivers sInstance = new CrappyMtkTabletWithBrokenTouchDrivers(); - } - public void process(final MotionEvent event) - { - if( (event.getAction() & MotionEvent.ACTION_MASK) != MotionEvent.ACTION_HOVER_MOVE && - (event.getAction() & MotionEvent.ACTION_MASK) != MotionEvent.ACTION_HOVER_EXIT ) // Ignore hover events, they are broken - super.process(event); - } - public void processGenericEvent(final MotionEvent event) - { - if( (event.getAction() & MotionEvent.ACTION_MASK) != MotionEvent.ACTION_HOVER_MOVE && - (event.getAction() & MotionEvent.ACTION_MASK) != MotionEvent.ACTION_HOVER_EXIT ) // Ignore hover events, they are broken - super.processGenericEvent(event); - } - } - private static class AutoDetectTouchInput extends IcsTouchInput - { - int tapCount = 0; - boolean hover = false, fingerHover = false, tap = false; - float hoverX = 0.0f, hoverY = 0.0f; - long hoverTime = 0; - float tapX = 0.0f, tapY = 0.0f; - long tapTime = 0; - float hoverTouchDistance = 0.0f; + GingerbreadTouchInput() { + super(); + } - private static class Holder - { - private static final AutoDetectTouchInput sInstance = new AutoDetectTouchInput(); - } - public void process(final MotionEvent event) - { - if( ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP || - (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) ) - { - tapCount ++; - if( (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP ) - { - tap = true; - tapX = event.getX(); - tapY = event.getY(); - tapTime = System.currentTimeMillis(); - if( hover ) - Log.i("SDL", "Tap tapX " + event.getX() + " tapY " + event.getX()); - } - else if( hover && System.currentTimeMillis() < hoverTime + 1000 ) - { - hoverTouchDistance += Math.abs(hoverX - event.getX()) + Math.abs(hoverY - event.getY()); - Log.i("SDL", "Finger down event.getX() " + event.getX() + " hoverX " + hoverX + " event.getY() " + event.getY() + " hoverY " + hoverY + " hoverTouchDistance " + hoverTouchDistance); - } - } - if( tapCount >= 4 ) - { - int displayHeight = 800; - try { - DisplayMetrics dm = new DisplayMetrics(); - MainActivity.instance.getWindowManager().getDefaultDisplay().getMetrics(dm); - displayHeight = Math.min(dm.widthPixels, dm.heightPixels); - } catch (Exception eeeee) {} - Log.i("SDL", "AutoDetectTouchInput: hoverTouchDistance " + hoverTouchDistance + " threshold " + displayHeight / 2 + " hover " + hover + " fingerHover " + fingerHover); - if( hoverTouchDistance > displayHeight / 2 ) - { - if( Globals.AppUsesMouse ) - Toast.makeText(MainActivity.instance, "Detected buggy touch panel, enabling workarounds", Toast.LENGTH_SHORT).show(); - touchInput = CrappyMtkTabletWithBrokenTouchDrivers.Holder.sInstance; - } - else - { - if( fingerHover ) - { - if( Globals.AppUsesMouse ) - Toast.makeText(MainActivity.instance, "Finger hover capability detected", Toast.LENGTH_SHORT).show(); - // Switch away from relative mouse input - if( Globals.FingerHover && (Globals.RelativeMouseMovement || Globals.LeftClickMethod != Mouse.LEFT_CLICK_NORMAL) ) - { - if( Globals.RelativeMouseMovement ) - Globals.ShowScreenUnderFinger = Mouse.ZOOM_MAGNIFIER; - Globals.RelativeMouseMovement = false; - Globals.LeftClickMethod = Mouse.LEFT_CLICK_NORMAL; - } - Settings.applyMouseEmulationOptions(); - } - if ( Globals.GenerateSubframeTouchEvents ) - touchInput = IcsTouchInputWithHistory.Holder.sInstance; - else - touchInput = IcsTouchInput.Holder.sInstance; - } - } - super.process(event); - } - public void processGenericEvent(final MotionEvent event) - { - super.processGenericEvent(event); - if( (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_HOVER_MOVE || - (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_HOVER_ENTER || - (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_HOVER_EXIT ) - { - hover = true; - hoverX = event.getX(); - hoverY = event.getY(); - hoverTime = System.currentTimeMillis(); - if( ExternalMouseDetected == 0 && (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_HOVER_MOVE ) - fingerHover = true; - if( tap && System.currentTimeMillis() < tapTime + 1000 ) - { - tap = false; - hoverTouchDistance += Math.abs(tapX - hoverX) + Math.abs(tapY - hoverY); - Log.i("SDL", "Hover hoverX " + hoverX + " tapX " + tapX + " hoverY " + hoverX + " tapY " + tapY + " hoverTouchDistance " + hoverTouchDistance); - } - } - } - } + public void process(final MotionEvent event) { + int hwMouseEvent = ((event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE || Globals.ForceHardwareMouse) ? Mouse.MOUSE_HW_INPUT_MOUSE : + ((event.getSource() & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) ? Mouse.MOUSE_HW_INPUT_STYLUS : + Mouse.MOUSE_HW_INPUT_FINGER; + if (ExternalMouseDetected != hwMouseEvent) { + ExternalMouseDetected = hwMouseEvent; + DemoGLSurfaceView.nativeHardwareMouseDetected(hwMouseEvent); + } + super.process(event); + if (!Globals.FingerHover && ExternalMouseDetected == Mouse.MOUSE_HW_INPUT_FINGER) + return; // Finger hover disabled in settings + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_HOVER_MOVE) // Support bluetooth/USB mouse - available since Android 3.1 + { + int action; + // TODO: it is possible that multiple pointers return that event, but we're handling only pointer #0 + if (touchEvents[0].down) + action = Mouse.SDL_FINGER_UP; + else + action = Mouse.SDL_FINGER_HOVER; + touchEvents[0].down = false; + touchEvents[0].x = (int) event.getX(); + touchEvents[0].y = (int) event.getY(); + touchEvents[0].pressure = Mouse.MAX_HOVER_DISTANCE; + touchEvents[0].size = 0; + //if( event.getAxisValue(MotionEvent.AXIS_DISTANCE) != 0.0f ) + InputDevice device = InputDevice.getDevice(event.getDeviceId()); + if (device != null && device.getMotionRange(MotionEvent.AXIS_DISTANCE) != null && + device.getMotionRange(MotionEvent.AXIS_DISTANCE).getRange() > 0.0f) + touchEvents[0].pressure = (int) ((event.getAxisValue(MotionEvent.AXIS_DISTANCE) - + device.getMotionRange(MotionEvent.AXIS_DISTANCE).getMin()) * Mouse.MAX_PRESSURE / device.getMotionRange(MotionEvent.AXIS_DISTANCE).getRange()); + DemoGLSurfaceView.nativeMotionEvent(touchEvents[0].x, touchEvents[0].y, action, 0, touchEvents[0].pressure, touchEvents[0].size); + } + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_HOVER_EXIT) // Update screen for finger hover + { + touchEvents[0].pressure = Mouse.HOVER_REDRAW_SCREEN; + touchEvents[0].size = 0; + DemoGLSurfaceView.nativeMotionEvent(touchEvents[0].x, touchEvents[0].y, Mouse.SDL_FINGER_HOVER, 0, touchEvents[0].pressure, touchEvents[0].size); + } + } - private static int gamepadIds[] = new int[4]; // Maximum 4 gamepads at the moment + public void processGenericEvent(final MotionEvent event) { + process(event); + } + } - public static int processGamepadDeviceId(InputDevice device) - { - if( device == null ) - return 0; - int source = device.getSources(); - if( (source & InputDevice.SOURCE_CLASS_JOYSTICK) != InputDevice.SOURCE_CLASS_JOYSTICK && - (source & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD ) - { - return 0; - } - int deviceId = device.getId(); - for( int i = 0; i < gamepadIds.length; i++ ) - { - if (gamepadIds[i] == deviceId) - return i + 1; - } - for( int i = 0; i < gamepadIds.length; i++ ) - { - if (gamepadIds[i] == 0) - { - Log.i("SDL", "libSDL: gamepad added: deviceId " + deviceId + " gamepadId " + (i + 1)); - gamepadIds[i] = deviceId; - return i + 1; - } - } - return 0; - } + private static class IcsTouchInput extends GingerbreadTouchInput { + private static class Holder { + private static final IcsTouchInput sInstance = new IcsTouchInput(); + } - public static void registerInputManagerCallbacks(Context context) - { - if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN ) - { - JellyBeanInputManager.Holder.sInstance.register(context); - } - } + private int buttonState = 0; - private static class JellyBeanInputManager - { - private static class Holder - { - private static final JellyBeanInputManager sInstance = new JellyBeanInputManager(); - } - private static class Listener implements InputManager.InputDeviceListener - { - public void onInputDeviceAdded(int deviceId) - { - } - public void onInputDeviceChanged(int deviceId) - { - onInputDeviceRemoved(deviceId); - } - public void onInputDeviceRemoved(int deviceId) - { - for( int i = 0; i < gamepadIds.length; i++ ) - { - if (gamepadIds[i] == deviceId) - { - Log.i("SDL", "libSDL: gamepad removed: deviceId " + deviceId + " gamepadId "+ (i + 1)); - gamepadIds[i] = 0; - } - } - } - } - public void register(Context context) - { - InputManager manager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); - manager.registerInputDeviceListener(new Listener(), null); - } - } + public void process(final MotionEvent event) { + //Log.i("SDL", "Got motion event, type " + (int)(event.getAction()) + " X " + (int)event.getX() + " Y " + (int)event.getY() + " buttons " + buttonState + " source " + event.getSource()); + int buttonStateNew = event.getButtonState(); + if (buttonStateNew != buttonState) { + for (int i = 1; i <= MotionEvent.BUTTON_FORWARD; i *= 2) { + if ((buttonStateNew & i) != (buttonState & i)) + DemoGLSurfaceView.nativeMouseButtonsPressed(i, ((buttonStateNew & i) == 0) ? 0 : 1); + } + if ((buttonStateNew & MotionEvent.BUTTON_STYLUS_PRIMARY) != (buttonState & MotionEvent.BUTTON_STYLUS_PRIMARY)) + DemoGLSurfaceView.nativeMouseButtonsPressed(2, ((buttonStateNew & MotionEvent.BUTTON_STYLUS_PRIMARY) == 0) ? 0 : 1); + if ((buttonStateNew & MotionEvent.BUTTON_STYLUS_SECONDARY) != (buttonState & MotionEvent.BUTTON_STYLUS_SECONDARY)) + DemoGLSurfaceView.nativeMouseButtonsPressed(4, ((buttonStateNew & MotionEvent.BUTTON_STYLUS_SECONDARY) == 0) ? 0 : 1); + buttonState = buttonStateNew; + } + super.process(event); + } + + public void processGenericEvent(final MotionEvent event) { + // Joysticks are supported since Honeycomb, but I don't care about it, because very few devices have it + if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) { + // event.getAxisValue(AXIS_HAT_X) and event.getAxisValue(AXIS_HAT_Y) are joystick arrow keys, on Nvidia Shield and some other joysticks + DemoGLSurfaceView.nativeGamepadAnalogJoystickInput( + event.getAxisValue(MotionEvent.AXIS_X), event.getAxisValue(MotionEvent.AXIS_Y), + event.getAxisValue(MotionEvent.AXIS_Z), event.getAxisValue(MotionEvent.AXIS_RZ), + event.getAxisValue(MotionEvent.AXIS_LTRIGGER), event.getAxisValue(MotionEvent.AXIS_RTRIGGER), + event.getAxisValue(MotionEvent.AXIS_HAT_X), event.getAxisValue(MotionEvent.AXIS_HAT_Y), + processGamepadDeviceId(event.getDevice())); + return; + } + // Process mousewheel + if (event.getAction() == MotionEvent.ACTION_SCROLL) { + int scrollX = Math.round(event.getAxisValue(MotionEvent.AXIS_HSCROLL)); + int scrollY = Math.round(event.getAxisValue(MotionEvent.AXIS_VSCROLL)); + DemoGLSurfaceView.nativeMouseWheel(scrollX, scrollY); + return; + } + super.processGenericEvent(event); + } + } + + private static class IcsTouchInputWithHistory extends IcsTouchInput { + private static class Holder { + private static final IcsTouchInputWithHistory sInstance = new IcsTouchInputWithHistory(); + } + + public void process(final MotionEvent event) { + int ptr = 0; // Process only one touch event, because that's typically a pen/mouse + for (ptr = 0; ptr < TOUCH_EVENTS_MAX; ptr++) { + if (touchEvents[ptr].down) + break; + } + if (ptr >= TOUCH_EVENTS_MAX) + ptr = 0; + //Log.i("SDL", "Got motion event, getHistorySize " + (int)(event.getHistorySize()) + " ptr " + ptr); + + for (int i = 0; i < event.getHistorySize(); i++) { + DemoGLSurfaceView.nativeMotionEvent((int) event.getHistoricalX(i), (int) event.getHistoricalY(i), + Mouse.SDL_FINGER_MOVE, ptr, (int) (event.getHistoricalPressure(i) * Mouse.MAX_PRESSURE), (int) (event.getHistoricalSize(i) * Mouse.MAX_PRESSURE)); + } + super.process(event); + } + } + + private static class CrappyMtkTabletWithBrokenTouchDrivers extends IcsTouchInput { + private static class Holder { + private static final CrappyMtkTabletWithBrokenTouchDrivers sInstance = new CrappyMtkTabletWithBrokenTouchDrivers(); + } + + public void process(final MotionEvent event) { + if ((event.getAction() & MotionEvent.ACTION_MASK) != MotionEvent.ACTION_HOVER_MOVE && + (event.getAction() & MotionEvent.ACTION_MASK) != MotionEvent.ACTION_HOVER_EXIT) // Ignore hover events, they are broken + super.process(event); + } + + public void processGenericEvent(final MotionEvent event) { + if ((event.getAction() & MotionEvent.ACTION_MASK) != MotionEvent.ACTION_HOVER_MOVE && + (event.getAction() & MotionEvent.ACTION_MASK) != MotionEvent.ACTION_HOVER_EXIT) // Ignore hover events, they are broken + super.processGenericEvent(event); + } + } + + private static class AutoDetectTouchInput extends IcsTouchInput { + int tapCount = 0; + boolean hover = false, fingerHover = false, tap = false; + float hoverX = 0.0f, hoverY = 0.0f; + long hoverTime = 0; + float tapX = 0.0f, tapY = 0.0f; + long tapTime = 0; + float hoverTouchDistance = 0.0f; + + private static class Holder { + private static final AutoDetectTouchInput sInstance = new AutoDetectTouchInput(); + } + + public void process(final MotionEvent event) { + if (((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP || + (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN)) { + tapCount++; + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { + tap = true; + tapX = event.getX(); + tapY = event.getY(); + tapTime = System.currentTimeMillis(); + if (hover) + Log.i("SDL", "Tap tapX " + event.getX() + " tapY " + event.getX()); + } else if (hover && System.currentTimeMillis() < hoverTime + 1000) { + hoverTouchDistance += Math.abs(hoverX - event.getX()) + Math.abs(hoverY - event.getY()); + Log.i("SDL", "Finger down event.getX() " + event.getX() + " hoverX " + hoverX + " event.getY() " + event.getY() + " hoverY " + hoverY + " hoverTouchDistance " + hoverTouchDistance); + } + } + if (tapCount >= 4) { + int displayHeight = 800; + try { + DisplayMetrics dm = new DisplayMetrics(); + MainActivity.instance.getWindowManager().getDefaultDisplay().getMetrics(dm); + displayHeight = Math.min(dm.widthPixels, dm.heightPixels); + } catch (Exception eeeee) { + } + Log.i("SDL", "AutoDetectTouchInput: hoverTouchDistance " + hoverTouchDistance + " threshold " + displayHeight / 2 + " hover " + hover + " fingerHover " + fingerHover); + if (hoverTouchDistance > displayHeight / 2) { + if (Globals.AppUsesMouse) + Toast.makeText(MainActivity.instance, "Detected buggy touch panel, enabling workarounds", Toast.LENGTH_SHORT).show(); + touchInput = CrappyMtkTabletWithBrokenTouchDrivers.Holder.sInstance; + } else { + if (fingerHover) { + if (Globals.AppUsesMouse) + Toast.makeText(MainActivity.instance, "Finger hover capability detected", Toast.LENGTH_SHORT).show(); + // Switch away from relative mouse input + if (Globals.FingerHover && (Globals.RelativeMouseMovement || Globals.LeftClickMethod != Mouse.LEFT_CLICK_NORMAL)) { + if (Globals.RelativeMouseMovement) + Globals.ShowScreenUnderFinger = Mouse.ZOOM_MAGNIFIER; + Globals.RelativeMouseMovement = false; + Globals.LeftClickMethod = Mouse.LEFT_CLICK_NORMAL; + } + Settings.applyMouseEmulationOptions(); + } + if (Globals.GenerateSubframeTouchEvents) + touchInput = IcsTouchInputWithHistory.Holder.sInstance; + else + touchInput = IcsTouchInput.Holder.sInstance; + } + } + super.process(event); + } + + public void processGenericEvent(final MotionEvent event) { + super.processGenericEvent(event); + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_HOVER_MOVE || + (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_HOVER_ENTER || + (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_HOVER_EXIT) { + hover = true; + hoverX = event.getX(); + hoverY = event.getY(); + hoverTime = System.currentTimeMillis(); + if (ExternalMouseDetected == 0 && (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_HOVER_MOVE) + fingerHover = true; + if (tap && System.currentTimeMillis() < tapTime + 1000) { + tap = false; + hoverTouchDistance += Math.abs(tapX - hoverX) + Math.abs(tapY - hoverY); + Log.i("SDL", "Hover hoverX " + hoverX + " tapX " + tapX + " hoverY " + hoverX + " tapY " + tapY + " hoverTouchDistance " + hoverTouchDistance); + } + } + } + } + + private static int gamepadIds[] = new int[4]; // Maximum 4 gamepads at the moment + + public static int processGamepadDeviceId(InputDevice device) { + if (device == null) + return 0; + int source = device.getSources(); + if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != InputDevice.SOURCE_CLASS_JOYSTICK && + (source & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD) { + return 0; + } + int deviceId = device.getId(); + for (int i = 0; i < gamepadIds.length; i++) { + if (gamepadIds[i] == deviceId) + return i + 1; + } + for (int i = 0; i < gamepadIds.length; i++) { + if (gamepadIds[i] == 0) { + Log.i("SDL", "libSDL: gamepad added: deviceId " + deviceId + " gamepadId " + (i + 1)); + gamepadIds[i] = deviceId; + return i + 1; + } + } + return 0; + } + + public static void registerInputManagerCallbacks(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + JellyBeanInputManager.Holder.sInstance.register(context); + } + } + + private static class JellyBeanInputManager { + private static class Holder { + private static final JellyBeanInputManager sInstance = new JellyBeanInputManager(); + } + + private static class Listener implements InputManager.InputDeviceListener { + public void onInputDeviceAdded(int deviceId) { + } + + public void onInputDeviceChanged(int deviceId) { + onInputDeviceRemoved(deviceId); + } + + public void onInputDeviceRemoved(int deviceId) { + for (int i = 0; i < gamepadIds.length; i++) { + if (gamepadIds[i] == deviceId) { + Log.i("SDL", "libSDL: gamepad removed: deviceId " + deviceId + " gamepadId " + (i + 1)); + gamepadIds[i] = 0; + } + } + } + } + + public void register(Context context) { + InputManager manager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); + manager.registerInputDeviceListener(new Listener(), null); + } + } } @SuppressWarnings("JniMissingFunction") -class DemoRenderer extends GLSurfaceView_SDL.Renderer -{ - public DemoRenderer(NeoXorgViewClient client) - { - mClient = client; - Clipboard.get().setListener(mClient.getContext(), new Runnable() - { - public void run() - { - nativeClipboardChanged(); - } - }); - } - - public void onSurfaceCreated(GL10 gl, EGLConfig config) - { - Log.i("SDL", "libSDL: DemoRenderer.onSurfaceCreated(): paused " + mPaused + " mFirstTimeStart " + mFirstTimeStart ); - mGlSurfaceCreated = true; - mGl = gl; - if( ! mPaused && ! mFirstTimeStart ) - nativeGlContextRecreated(); - mFirstTimeStart = false; - } +class DemoRenderer extends GLSurfaceView_SDL.Renderer { + public DemoRenderer(NeoXorgViewClient client) { + mClient = client; + Clipboard.get().setListener(mClient.getContext(), new Runnable() { + public void run() { + nativeClipboardChanged(); + } + }); + } - public void onSurfaceChanged(GL10 gl, int w, int h) - { - Log.i("SDL", "libSDL: DemoRenderer.onSurfaceChanged(): paused " + mPaused + " mFirstTimeStart " + mFirstTimeStart + " w " + w + " h " + h); - if( w < h && Globals.HorizontalOrientation ) - { - // Sometimes when Android awakes from lockscreen, portrait orientation is kept - int x = w; - w = h; - h = x; - } - mWidth = w - w % 2; - mHeight = h - h % 2; - mGl = gl; - nativeResize(mWidth, mHeight, Globals.KeepAspectRatio ? 1 : 0); - } + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + Log.i("SDL", "libSDL: DemoRenderer.onSurfaceCreated(): paused " + mPaused + " mFirstTimeStart " + mFirstTimeStart); + mGlSurfaceCreated = true; + mGl = gl; + if (!mPaused && !mFirstTimeStart) + nativeGlContextRecreated(); + mFirstTimeStart = false; + } - int mLastPendingResize = 0; - public void onWindowResize(final int w, final int h) - { - if (mClient.isRunningOnOUYA()) - return; // TV screen is never resized, and this event will mess up TV borders - Log.d("SDL", "libSDL: DemoRenderer.onWindowResize(): " + w + "x" + h); - mLastPendingResize ++; - final int resizeThreadIndex = mLastPendingResize; - mClient.getGLView().postDelayed(new Runnable() - { - public void run() - { - // Samsung multiwindow will swap screen dimensions when unlocking the lockscreen, sleep a while so we won't use these temporary values - if (resizeThreadIndex != mLastPendingResize) - return; // Avoid running this function multiple times in a row - int ww = w - w % 2; - int hh = h - h % 2; - View topView = mClient.getWindow().peekDecorView(); - if (topView != null && Globals.ImmersiveMode) - { - ww = topView.getWidth() - topView.getWidth() % 2; - hh = topView.getHeight() - topView.getHeight() % 2; - } + public void onSurfaceChanged(GL10 gl, int w, int h) { + Log.i("SDL", "libSDL: DemoRenderer.onSurfaceChanged(): paused " + mPaused + " mFirstTimeStart " + mFirstTimeStart + " w " + w + " h " + h); + if (w < h && Globals.HorizontalOrientation) { + // Sometimes when Android awakes from lockscreen, portrait orientation is kept + int x = w; + w = h; + h = x; + } + mWidth = w - w % 2; + mHeight = h - h % 2; + mGl = gl; + nativeResize(mWidth, mHeight, Globals.KeepAspectRatio ? 1 : 0); + } - Display display = mClient.getWindowManager().getDefaultDisplay(); + int mLastPendingResize = 0; - if (mWidth != 0 && mHeight != 0 && (mWidth != ww || mHeight != hh)) - { - Log.i("SDL", "libSDL: DemoRenderer.onWindowResize(): screen size changed from " + mWidth + "x" + mHeight + " to " + ww + "x" + hh); - if (Globals.SwVideoMode && - (Math.abs(display.getWidth() - ww) > display.getWidth() / 10 || - Math.abs(display.getHeight() - hh) > display.getHeight() / 10)) - { - Log.i("SDL", "Multiwindow detected - enabling screen orientation autodetection"); - Globals.AutoDetectOrientation = true; - mClient.initScreenOrientation(); - DemoRenderer.super.ResetVideoSurface(); - DemoRenderer.super.onWindowResize(ww, hh); - } - else - { - Log.i("SDL", "System button bar hidden - re-init video to avoid black bar at the top"); - DemoRenderer.super.ResetVideoSurface(); - DemoRenderer.super.onWindowResize(ww, hh); - } - } - if (mWidth == 0 && mHeight == 0) - { - if ((ww > hh) != (display.getWidth() > display.getHeight())) - { - Log.i("SDL", "Multiwindow detected - app window size " + ww + "x" + hh + " but display dimensions are " + display.getWidth() + "x" + display.getHeight()); - Globals.AutoDetectOrientation = true; - } - } - if (Globals.AutoDetectOrientation && (ww > hh) != (mWidth > mHeight)) - Globals.HorizontalOrientation = (ww > hh); - } - }, 2000); - } + public void onWindowResize(final int w, final int h) { + if (mClient.isRunningOnOUYA()) + return; // TV screen is never resized, and this event will mess up TV borders + Log.d("SDL", "libSDL: DemoRenderer.onWindowResize(): " + w + "x" + h); + mLastPendingResize++; + final int resizeThreadIndex = mLastPendingResize; + mClient.getGLView().postDelayed(new Runnable() { + public void run() { + // Samsung multiwindow will swap screen dimensions when unlocking the lockscreen, sleep a while so we won't use these temporary values + if (resizeThreadIndex != mLastPendingResize) + return; // Avoid running this function multiple times in a row + int ww = w - w % 2; + int hh = h - h % 2; + View topView = mClient.getWindow().peekDecorView(); + if (topView != null && Globals.ImmersiveMode) { + ww = topView.getWidth() - topView.getWidth() % 2; + hh = topView.getHeight() - topView.getHeight() % 2; + } - public void onSurfaceDestroyed() - { - Log.i("SDL", "libSDL: DemoRenderer.onSurfaceDestroyed(): paused " + mPaused + " mFirstTimeStart " + mFirstTimeStart ); - mGlSurfaceCreated = false; - mGlContextLost = true; - nativeGlContextLost(); - }; + Display display = mClient.getWindowManager().getDefaultDisplay(); - public void onDrawFrame(GL10 gl) - { - mGl = gl; - SwapBuffers(); + if (mWidth != 0 && mHeight != 0 && (mWidth != ww || mHeight != hh)) { + Log.i("SDL", "libSDL: DemoRenderer.onWindowResize(): screen size changed from " + mWidth + "x" + mHeight + " to " + ww + "x" + hh); + if (Globals.SwVideoMode && + (Math.abs(display.getWidth() - ww) > display.getWidth() / 10 || + Math.abs(display.getHeight() - hh) > display.getHeight() / 10)) { + Log.i("SDL", "Multiwindow detected - enabling screen orientation autodetection"); + Globals.AutoDetectOrientation = true; + mClient.initScreenOrientation(); + DemoRenderer.super.ResetVideoSurface(); + DemoRenderer.super.onWindowResize(ww, hh); + } else { + Log.i("SDL", "System button bar hidden - re-init video to avoid black bar at the top"); + DemoRenderer.super.ResetVideoSurface(); + DemoRenderer.super.onWindowResize(ww, hh); + } + } + if (mWidth == 0 && mHeight == 0) { + if ((ww > hh) != (display.getWidth() > display.getHeight())) { + Log.i("SDL", "Multiwindow detected - app window size " + ww + "x" + hh + " but display dimensions are " + display.getWidth() + "x" + display.getHeight()); + Globals.AutoDetectOrientation = true; + } + } + if (Globals.AutoDetectOrientation && (ww > hh) != (mWidth > mHeight)) + Globals.HorizontalOrientation = (ww > hh); + } + }, 2000); + } - nativeInitJavaCallbacks(); - - // Make main thread priority lower so audio thread won't get underrun - // Thread.currentThread().setPriority((Thread.currentThread().getPriority() + Thread.MIN_PRIORITY)/2); - - mGlContextLost = false; + public void onSurfaceDestroyed() { + Log.i("SDL", "libSDL: DemoRenderer.onSurfaceDestroyed(): paused " + mPaused + " mFirstTimeStart " + mFirstTimeStart); + mGlSurfaceCreated = false; + mGlContextLost = true; + nativeGlContextLost(); + } - Settings.Apply(mClient); - Settings.nativeSetEnv( "DISPLAY_RESOLUTION_WIDTH", String.valueOf(Math.max(mWidth, mHeight)) ); - Settings.nativeSetEnv( "DISPLAY_RESOLUTION_HEIGHT", String.valueOf(Math.min(mWidth, mHeight)) ); // In Kitkat with immersive mode, getWindowManager().getDefaultDisplay().getMetrics() return inaccurate height + ; - accelerometer = new NeoAccelerometerReader(mClient.getContext()); - if( Globals.MoveMouseWithGyroscope ) - startAccelerometerGyroscope(1); - // Tweak video thread priority, if user selected big audio buffer - if( Globals.AudioBufferConfig >= 2 ) - Thread.currentThread().setPriority( (Thread.NORM_PRIORITY + Thread.MIN_PRIORITY) / 2 ); // Lower than normal - // Calls main() and never returns, hehe - we'll call eglSwapBuffers() from native code - String commandline = Globals.CommandLine; + public void onDrawFrame(GL10 gl) { + mGl = gl; + SwapBuffers(); + + nativeInitJavaCallbacks(); + + // Make main thread priority lower so audio thread won't get underrun + // Thread.currentThread().setPriority((Thread.currentThread().getPriority() + Thread.MIN_PRIORITY)/2); + + mGlContextLost = false; + + Settings.Apply(mClient); + Settings.nativeSetEnv("DISPLAY_RESOLUTION_WIDTH", String.valueOf(Math.max(mWidth, mHeight))); + Settings.nativeSetEnv("DISPLAY_RESOLUTION_HEIGHT", String.valueOf(Math.min(mWidth, mHeight))); // In Kitkat with immersive mode, getWindowManager().getDefaultDisplay().getMetrics() return inaccurate height + + accelerometer = new NeoAccelerometerReader(mClient.getContext()); + if (Globals.MoveMouseWithGyroscope) + startAccelerometerGyroscope(1); + // Tweak video thread priority, if user selected big audio buffer + if (Globals.AudioBufferConfig >= 2) + Thread.currentThread().setPriority((Thread.NORM_PRIORITY + Thread.MIN_PRIORITY) / 2); // Lower than normal + // Calls main() and never returns, hehe - we'll call eglSwapBuffers() from native code + String commandline = Globals.CommandLine; // if( mClient.getIntent() != null && mClient.getIntent().getScheme() != null && // mClient.getIntent().getScheme().compareTo(android.content.ContentResolver.SCHEME_FILE) == 0 && // mClient.getIntent().getData() != null && mClient.getIntent().getData().getPath() != null ) // { // commandline += " " + mClient.getIntent().getData().getPath(); // } - nativeInit( Globals.DataDir, - commandline, - ( (Globals.SwVideoMode && Globals.MultiThreadedVideo) || Globals.CompatibilityHacksVideo ) ? 1 : 0, - 0 ); - System.exit(0); // The main() returns here - I don't bother with deinit stuff, just terminate process - } + nativeInit(Globals.DataDir, + commandline, + ((Globals.SwVideoMode && Globals.MultiThreadedVideo) || Globals.CompatibilityHacksVideo) ? 1 : 0, + 0); + System.exit(0); // The main() returns here - I don't bother with deinit stuff, just terminate process + } - public int swapBuffers() // Called from native code - { - if( ! super.SwapBuffers() && Globals.NonBlockingSwapBuffers ) - { - if(mRatelimitTouchEvents) - { - synchronized(this) - { - this.notify(); - } - } - return 0; - } + public int swapBuffers() // Called from native code + { + if (!super.SwapBuffers() && Globals.NonBlockingSwapBuffers) { + if (mRatelimitTouchEvents) { + synchronized (this) { + this.notify(); + } + } + return 0; + } - if(mGlContextLost) { - mGlContextLost = false; - Settings.SetupTouchscreenKeyboardGraphics(mClient.getContext()); // Reload on-screen buttons graphics - super.SwapBuffers(); - } + if (mGlContextLost) { + mGlContextLost = false; + Settings.SetupTouchscreenKeyboardGraphics(mClient.getContext()); // Reload on-screen buttons graphics + super.SwapBuffers(); + } - // Unblock event processing thread only after we've finished rendering - if(mRatelimitTouchEvents) - { - synchronized(this) - { - this.notify(); - } - } - if( mClient.isScreenKeyboardShown() && !mClient.isKeyboardWithoutTextInputShown() ) - { - try { - Thread.sleep(50); // Give some time to the keyboard input thread - } catch(Exception e) { }; - } + // Unblock event processing thread only after we've finished rendering + if (mRatelimitTouchEvents) { + synchronized (this) { + this.notify(); + } + } + if (mClient.isScreenKeyboardShown() && !mClient.isKeyboardWithoutTextInputShown()) { + try { + Thread.sleep(50); // Give some time to the keyboard input thread + } catch (Exception e) { + } + ; + } - // We will not receive onConfigurationChanged() inside MainActivity with SCREEN_ORIENTATION_SENSOR_LANDSCAPE - // so we need to create a hacky frame counter to update screen orientation, because this call takes up some time - mOrientationFrameHackyCounter++; - if( mOrientationFrameHackyCounter > 100 ) - { - mOrientationFrameHackyCounter = 0; - mClient.updateScreenOrientation(); - } + // We will not receive onConfigurationChanged() inside MainActivity with SCREEN_ORIENTATION_SENSOR_LANDSCAPE + // so we need to create a hacky frame counter to update screen orientation, because this call takes up some time + mOrientationFrameHackyCounter++; + if (mOrientationFrameHackyCounter > 100) { + mOrientationFrameHackyCounter = 0; + mClient.updateScreenOrientation(); + } - return 1; - } + return 1; + } - public void showScreenKeyboardWithoutTextInputField() // Called from native code - { - mClient.showScreenKeyboardWithoutTextInputField(Globals.TextInputKeyboard); - } + public void showScreenKeyboardWithoutTextInputField() // Called from native code + { + mClient.showScreenKeyboardWithoutTextInputField(Globals.TextInputKeyboard); + } - public void showInternalScreenKeyboard(int keyboard) // Called from native code - { - mClient.showScreenKeyboardWithoutTextInputField(keyboard); - } + public void showInternalScreenKeyboard(int keyboard) // Called from native code + { + mClient.showScreenKeyboardWithoutTextInputField(keyboard); + } - public void showScreenKeyboard(final String oldText, int unused) // Called from native code - { - class Callback implements Runnable - { - public NeoXorgViewClient client; - public String oldText; - public void run() - { - client.showScreenKeyboard(oldText); - } - } - Callback cb = new Callback(); - cb.client = mClient; - cb.oldText = oldText; - mClient.runOnUiThread(cb); - } + public void showScreenKeyboard(final String oldText, int unused) // Called from native code + { + class Callback implements Runnable { + public NeoXorgViewClient client; + public String oldText; - public void hideScreenKeyboard() // Called from native code - { - class Callback implements Runnable - { - public NeoXorgViewClient client; - public void run() - { - client.hideScreenKeyboard(); - } - } - Callback cb = new Callback(); - cb.client = mClient; - mClient.runOnUiThread(cb); - } + public void run() { + client.showScreenKeyboard(oldText); + } + } + Callback cb = new Callback(); + cb.client = mClient; + cb.oldText = oldText; + mClient.runOnUiThread(cb); + } - public int isScreenKeyboardShown() // Called from native code - { - return mClient.isScreenKeyboardShown() ? 1 : 0; - } + public void hideScreenKeyboard() // Called from native code + { + class Callback implements Runnable { + public NeoXorgViewClient client; - public void setScreenKeyboardHintMessage(String s) // Called from native code - { - mClient.setScreenKeyboardHintMessage(s); - } + public void run() { + client.hideScreenKeyboard(); + } + } + Callback cb = new Callback(); + cb.client = mClient; + mClient.runOnUiThread(cb); + } - public void startAccelerometerGyroscope(int started) // Called from native code - { - accelerometer.openedBySDL = (started != 0); - if( accelerometer.openedBySDL && !mPaused ) - accelerometer.start(); - else - accelerometer.stop(); - } + public int isScreenKeyboardShown() // Called from native code + { + return mClient.isScreenKeyboardShown() ? 1 : 0; + } - public String getClipboardText() // Called from native code - { - return Clipboard.get().get(mClient.getContext()); - } + public void setScreenKeyboardHintMessage(String s) // Called from native code + { + mClient.setScreenKeyboardHintMessage(s); + } - public void setClipboardText(final String s) // Called from native code - { - Clipboard.get().set(mClient.getContext(), s); - } + public void startAccelerometerGyroscope(int started) // Called from native code + { + accelerometer.openedBySDL = (started != 0); + if (accelerometer.openedBySDL && !mPaused) + accelerometer.start(); + else + accelerometer.stop(); + } - public void exitApp() - { - nativeDone(); - } + public String getClipboardText() // Called from native code + { + return Clipboard.get().get(mClient.getContext()); + } - public void getAdvertisementParams(int params[]) - { - } - public void setAdvertisementVisible(int visible) - { + public void setClipboardText(final String s) // Called from native code + { + Clipboard.get().set(mClient.getContext(), s); + } - } - public void setAdvertisementPosition(int left, int top) - { - } - public void requestNewAdvertisement() - { - } + public void exitApp() { + nativeDone(); + } - public boolean cloudSave(final String filename, final String saveId, final String dialogTitle, final String description, final String imageFile, final long playedTimeMs) - { - return false; - } + public void getAdvertisementParams(int params[]) { + } - public boolean cloudLoad(String filename, String saveId, String dialogTitle) - { - return false; - } - - public void openExternalApp(String pkgName, String activity, String url) - { - try { - Intent i = new Intent(); - if (url != null && url.length() > 0) - { - i.setAction(Intent.ACTION_VIEW); - i.setData(Uri.parse(url)); - } - if (pkgName != null && activity != null && pkgName.length() > 0 && activity.length() > 0) - { - i.setClassName(pkgName, activity); - } - mClient.getContext().startActivity(i); - } catch (Exception e) { - Log.i("SDL", "libSDL: cannot start external app: " + e.toString()); - } - } + public void setAdvertisementVisible(int visible) { - public void setSystemMousePointerVisible(int visible) - { - mClient.setSystemMousePointerVisible(visible); - } + } - public void restartMyself(String restartParams) - { - } + public void setAdvertisementPosition(int left, int top) { + } - public void setConfigOptionFromSDL(int option, int value) - { - Settings.setConfigOptionFromSDL(option, value); - } + public void requestNewAdvertisement() { + } - private int PowerOf2(int i) - { - int value = 1; - while (value < i) - value <<= 1; - return value; - } + public boolean cloudSave(final String filename, final String saveId, final String dialogTitle, final String description, final String imageFile, final long playedTimeMs) { + return false; + } - private native void nativeInitJavaCallbacks(); - private native void nativeInit(String CurrentPath, String CommandLine, int multiThreadedVideo, int unused); - private native void nativeResize(int w, int h, int keepAspectRatio); - private native void nativeDone(); - private native void nativeGlContextLost(); - public native void nativeGlContextRecreated(); - public native void nativeGlContextLostAsyncEvent(); - public static native void nativeTextInput( int ascii, int unicode ); - public static native void nativeTextInputFinished(); - public static native void nativeClipboardChanged(); + public boolean cloudLoad(String filename, String saveId, String dialogTitle) { + return false; + } - private NeoXorgViewClient mClient = null; - public AccelerometerReader accelerometer = null; - - private GL10 mGl = null; - private EGL10 mEgl = null; - private EGLDisplay mEglDisplay = null; - private EGLSurface mEglSurface = null; - private EGLContext mEglContext = null; - private boolean mGlContextLost = false; - public boolean mGlSurfaceCreated = false; - public boolean mPaused = false; - private boolean mFirstTimeStart = true; - public int mWidth = 0; - public int mHeight = 0; - int mOrientationFrameHackyCounter = 0; + public void openExternalApp(String pkgName, String activity, String url) { + try { + Intent i = new Intent(); + if (url != null && url.length() > 0) { + i.setAction(Intent.ACTION_VIEW); + i.setData(Uri.parse(url)); + } + if (pkgName != null && activity != null && pkgName.length() > 0 && activity.length() > 0) { + i.setClassName(pkgName, activity); + } + mClient.getContext().startActivity(i); + } catch (Exception e) { + Log.i("SDL", "libSDL: cannot start external app: " + e.toString()); + } + } - public static final boolean mRatelimitTouchEvents = true; //(Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO); + public void setSystemMousePointerVisible(int visible) { + mClient.setSystemMousePointerVisible(visible); + } + + public void restartMyself(String restartParams) { + } + + public void setConfigOptionFromSDL(int option, int value) { + Settings.setConfigOptionFromSDL(option, value); + } + + private int PowerOf2(int i) { + int value = 1; + while (value < i) + value <<= 1; + return value; + } + + private native void nativeInitJavaCallbacks(); + + private native void nativeInit(String CurrentPath, String CommandLine, int multiThreadedVideo, int unused); + + private native void nativeResize(int w, int h, int keepAspectRatio); + + private native void nativeDone(); + + private native void nativeGlContextLost(); + + public native void nativeGlContextRecreated(); + + public native void nativeGlContextLostAsyncEvent(); + + public static native void nativeTextInput(int ascii, int unicode); + + public static native void nativeTextInputFinished(); + + public static native void nativeClipboardChanged(); + + private NeoXorgViewClient mClient = null; + public AccelerometerReader accelerometer = null; + + private GL10 mGl = null; + private EGL10 mEgl = null; + private EGLDisplay mEglDisplay = null; + private EGLSurface mEglSurface = null; + private EGLContext mEglContext = null; + private boolean mGlContextLost = false; + public boolean mGlSurfaceCreated = false; + public boolean mPaused = false; + private boolean mFirstTimeStart = true; + public int mWidth = 0; + public int mHeight = 0; + int mOrientationFrameHackyCounter = 0; + + public static final boolean mRatelimitTouchEvents = true; //(Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO); } @SuppressWarnings("JniMissingFunction") class DemoGLSurfaceView extends GLSurfaceView_SDL { - public DemoGLSurfaceView(NeoXorgViewClient client) { - super(client.getContext()); - mClient = client; - setEGLConfigChooser(Globals.VideoDepthBpp, Globals.NeedDepthBuffer, Globals.NeedStencilBuffer, Globals.NeedGles2, Globals.NeedGles3); - mRenderer = new DemoRenderer(client); - setRenderer(mRenderer); - DifferentTouchInput.registerInputManagerCallbacks(client.getContext()); - } + public DemoGLSurfaceView(NeoXorgViewClient client) { + super(client.getContext()); + mClient = client; + setEGLConfigChooser(Globals.VideoDepthBpp, Globals.NeedDepthBuffer, Globals.NeedStencilBuffer, Globals.NeedGles2, Globals.NeedGles3); + mRenderer = new DemoRenderer(client); + setRenderer(mRenderer); + DifferentTouchInput.registerInputManagerCallbacks(client.getContext()); + } - @Override - public boolean onKeyDown(int keyCode, final KeyEvent event) - { - if( keyCode == KeyEvent.KEYCODE_BACK ) - { - if( (event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE ) - { - // Stupid Samsung and stupid Acer remaps right mouse button to BACK key - nativeMouseButtonsPressed(2, 1); - return true; - } - else if( mClient.isKeyboardWithoutTextInputShown() ) - { - return true; - } - } + @Override + public boolean onKeyDown(int keyCode, final KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if ((event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { + // Stupid Samsung and stupid Acer remaps right mouse button to BACK key + nativeMouseButtonsPressed(2, 1); + return true; + } else if (mClient.isKeyboardWithoutTextInputShown()) { + return true; + } + } - if( nativeKey( keyCode, 1, event.getUnicodeChar(), DifferentTouchInput.processGamepadDeviceId(event.getDevice()) ) == 0 ) - return super.onKeyDown(keyCode, event); + if (nativeKey(keyCode, 1, event.getUnicodeChar(), DifferentTouchInput.processGamepadDeviceId(event.getDevice())) == 0) + return super.onKeyDown(keyCode, event); - return true; - } - - @Override - public boolean onKeyUp(int keyCode, final KeyEvent event) - { - if( keyCode == KeyEvent.KEYCODE_BACK ) - { - if( (event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE ) - { - // Stupid Samsung and stupid Acer remaps right mouse button to BACK key - nativeMouseButtonsPressed(2, 0); - return true; - } - else if( mClient.isKeyboardWithoutTextInputShown() ) - { - mClient.showScreenKeyboardWithoutTextInputField(0); // Hide keyboard - return true; - } - } + return true; + } - if( nativeKey( keyCode, 0, event.getUnicodeChar(), DifferentTouchInput.processGamepadDeviceId(event.getDevice()) ) == 0 ) - return super.onKeyUp(keyCode, event); + @Override + public boolean onKeyUp(int keyCode, final KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if ((event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { + // Stupid Samsung and stupid Acer remaps right mouse button to BACK key + nativeMouseButtonsPressed(2, 0); + return true; + } else if (mClient.isKeyboardWithoutTextInputShown()) { + mClient.showScreenKeyboardWithoutTextInputField(0); // Hide keyboard + return true; + } + } - //if( keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU ) - // DimSystemStatusBar.get().dim(mClient._videoLayout); + if (nativeKey(keyCode, 0, event.getUnicodeChar(), DifferentTouchInput.processGamepadDeviceId(event.getDevice())) == 0) + return super.onKeyUp(keyCode, event); - return true; - } + //if( keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU ) + // DimSystemStatusBar.get().dim(mClient._videoLayout); - @Override - public boolean onKeyMultiple(int keyCode, int repeatCount, final KeyEvent event) - { - if( event.getCharacters() != null ) - { - // International text input - for(int i = 0; i < event.getCharacters().length(); i++ ) - { - nativeKey( event.getKeyCode(), 1, event.getCharacters().codePointAt(i), 0 ); - nativeKey( event.getKeyCode(), 0, event.getCharacters().codePointAt(i), 0 ); - } - } - return true; - } + return true; + } - @Override - public boolean onTouchEvent(final MotionEvent event) - { - if (getX() != 0) - event.offsetLocation(-getX(), -getY()); - DifferentTouchInput.touchInput.process(event); - if( DemoRenderer.mRatelimitTouchEvents ) - { - limitEventRate(event); - } - return true; - }; + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, final KeyEvent event) { + if (event.getCharacters() != null) { + // International text input + for (int i = 0; i < event.getCharacters().length(); i++) { + nativeKey(event.getKeyCode(), 1, event.getCharacters().codePointAt(i), 0); + nativeKey(event.getKeyCode(), 0, event.getCharacters().codePointAt(i), 0); + } + } + return true; + } - @Override - public boolean onGenericMotionEvent (final MotionEvent event) - { - DifferentTouchInput.touchInput.processGenericEvent(event); - if( DemoRenderer.mRatelimitTouchEvents ) - { - limitEventRate(event); - } - return true; - } - - public void limitEventRate(final MotionEvent event) - { - // Wait a bit, and try to synchronize to app framerate, or event thread will eat all CPU and we'll lose FPS - // With Froyo the rate of touch events seems to be limited by OS, but they are arriving faster then we're redrawing anyway - if((event.getAction() == MotionEvent.ACTION_MOVE || - event.getAction() == MotionEvent.ACTION_HOVER_MOVE)) - { - synchronized(mRenderer) - { - try - { - mRenderer.wait(300L); // And sometimes the app decides not to render at all, so this timeout should not be big. - } - catch (InterruptedException e) - { - Log.v("SDL", "DemoGLSurfaceView::limitEventRate(): Who dared to interrupt my slumber?"); - Thread.interrupted(); - } - } - } - } + @Override + public boolean onTouchEvent(final MotionEvent event) { + if (getX() != 0) + event.offsetLocation(-getX(), -getY()); + DifferentTouchInput.touchInput.process(event); + if (DemoRenderer.mRatelimitTouchEvents) { + limitEventRate(event); + } + return true; + } - public void exitApp() { - mRenderer.exitApp(); - }; + ; - @Override - public void onPause() { - Log.i("SDL", "libSDL: DemoGLSurfaceView.onPause(): mRenderer.mGlSurfaceCreated " + mRenderer.mGlSurfaceCreated + " mRenderer.mPaused " + mRenderer.mPaused + (mRenderer.mPaused ? " - not doing anything" : "")); - if(mRenderer.mPaused) - return; - mRenderer.mPaused = true; - super.onPause(); - mRenderer.nativeGlContextLostAsyncEvent(); - if( mRenderer.accelerometer != null ) // For some reason it crashes here often - are we getting this event before initialization? - mRenderer.accelerometer.stop(); - }; - - public boolean isPaused() { - return mRenderer.mPaused; - } + @Override + public boolean onGenericMotionEvent(final MotionEvent event) { + DifferentTouchInput.touchInput.processGenericEvent(event); + if (DemoRenderer.mRatelimitTouchEvents) { + limitEventRate(event); + } + return true; + } - @Override - public void onResume() { - Log.i("SDL", "libSDL: DemoGLSurfaceView.onResume(): mRenderer.mGlSurfaceCreated " + mRenderer.mGlSurfaceCreated + " mRenderer.mPaused " + mRenderer.mPaused + (!mRenderer.mPaused ? " - not doing anything" : "")); - if(!mRenderer.mPaused) - return; - mRenderer.mPaused = false; - super.onResume(); - if( mRenderer.mGlSurfaceCreated && ! mRenderer.mPaused || Globals.NonBlockingSwapBuffers ) - mRenderer.nativeGlContextRecreated(); - if( mRenderer.accelerometer != null && mRenderer.accelerometer.openedBySDL ) // For some reason it crashes here often - are we getting this event before initialization? - mRenderer.accelerometer.start(); - }; + public void limitEventRate(final MotionEvent event) { + // Wait a bit, and try to synchronize to app framerate, or event thread will eat all CPU and we'll lose FPS + // With Froyo the rate of touch events seems to be limited by OS, but they are arriving faster then we're redrawing anyway + if ((event.getAction() == MotionEvent.ACTION_MOVE || + event.getAction() == MotionEvent.ACTION_HOVER_MOVE)) { + synchronized (mRenderer) { + try { + mRenderer.wait(300L); // And sometimes the app decides not to render at all, so this timeout should not be big. + } catch (InterruptedException e) { + Log.v("SDL", "DemoGLSurfaceView::limitEventRate(): Who dared to interrupt my slumber?"); + Thread.interrupted(); + } + } + } + } - DemoRenderer mRenderer; - NeoXorgViewClient mClient; + public void exitApp() { + mRenderer.exitApp(); + } - public static native void nativeMotionEvent( int x, int y, int action, int pointerId, int pressure, int radius ); - public static native int nativeKey( int keyCode, int down, int unicode, int gamepadId ); - public static native void nativeHardwareMouseDetected( int detected ); - public static native void nativeMouseButtonsPressed( int buttonId, int pressedState ); - public static native void nativeMouseWheel( int scrollX, int scrollY ); - public static native void nativeGamepadAnalogJoystickInput( float stick1x, float stick1y, float stick2x, float stick2y, float ltrigger, float rtrigger, float dpadx, float dpady, int gamepadId ); - public static native void nativeScreenVisibleRect( int x, int y, int w, int h ); - public static native void nativeScreenKeyboardShown( int shown ); + ; + + @Override + public void onPause() { + Log.i("SDL", "libSDL: DemoGLSurfaceView.onPause(): mRenderer.mGlSurfaceCreated " + mRenderer.mGlSurfaceCreated + " mRenderer.mPaused " + mRenderer.mPaused + (mRenderer.mPaused ? " - not doing anything" : "")); + if (mRenderer.mPaused) + return; + mRenderer.mPaused = true; + super.onPause(); + mRenderer.nativeGlContextLostAsyncEvent(); + if (mRenderer.accelerometer != null) // For some reason it crashes here often - are we getting this event before initialization? + mRenderer.accelerometer.stop(); + } + + ; + + public boolean isPaused() { + return mRenderer.mPaused; + } + + @Override + public void onResume() { + Log.i("SDL", "libSDL: DemoGLSurfaceView.onResume(): mRenderer.mGlSurfaceCreated " + mRenderer.mGlSurfaceCreated + " mRenderer.mPaused " + mRenderer.mPaused + (!mRenderer.mPaused ? " - not doing anything" : "")); + if (!mRenderer.mPaused) + return; + mRenderer.mPaused = false; + super.onResume(); + if (mRenderer.mGlSurfaceCreated && !mRenderer.mPaused || Globals.NonBlockingSwapBuffers) + mRenderer.nativeGlContextRecreated(); + if (mRenderer.accelerometer != null && mRenderer.accelerometer.openedBySDL) // For some reason it crashes here often - are we getting this event before initialization? + mRenderer.accelerometer.start(); + } + + ; + + DemoRenderer mRenderer; + NeoXorgViewClient mClient; + + public static native void nativeMotionEvent(int x, int y, int action, int pointerId, int pressure, int radius); + + public static native int nativeKey(int keyCode, int down, int unicode, int gamepadId); + + public static native void nativeHardwareMouseDetected(int detected); + + public static native void nativeMouseButtonsPressed(int buttonId, int pressedState); + + public static native void nativeMouseWheel(int scrollX, int scrollY); + + public static native void nativeGamepadAnalogJoystickInput(float stick1x, float stick1y, float stick2x, float stick2y, float ltrigger, float rtrigger, float dpadx, float dpady, int gamepadId); + + public static native void nativeScreenVisibleRect(int x, int y, int w, int h); + + public static native void nativeScreenKeyboardShown(int shown); } diff --git a/Xorg/src/main/java/io/neoterm/XZInputStream.java b/Xorg/src/main/java/io/neoterm/XZInputStream.java index d159fd8..b4bc240 100644 --- a/Xorg/src/main/java/io/neoterm/XZInputStream.java +++ b/Xorg/src/main/java/io/neoterm/XZInputStream.java @@ -22,11 +22,8 @@ freely, subject to the following restrictions: package io.neoterm; -import java.io.InputStream; -import java.io.DataInputStream; import java.io.IOException; -import java.io.EOFException; -import android.util.Log; +import java.io.InputStream; /** * Decompresses a .xz file in streamed mode (no seeking). @@ -34,128 +31,114 @@ import android.util.Log; * but using liblzma and JNI instead of Java, because Java heap * is very limited, and we're hitting memory limit on emulator. */ -public class XZInputStream extends InputStream -{ - private long nativeData = 0; - private InputStream in = null; - private final byte[] inBuf = new byte[8192]; - private int inOffset = 0; - private int inAvailable = 0; - private boolean outBufEof = false; - private int offsets[] = new int[2]; +public class XZInputStream extends InputStream { + private long nativeData = 0; + private InputStream in = null; + private final byte[] inBuf = new byte[8192]; + private int inOffset = 0; + private int inAvailable = 0; + private boolean outBufEof = false; + private int offsets[] = new int[2]; - private final byte[] tempBuf = new byte[1]; + private final byte[] tempBuf = new byte[1]; - public XZInputStream(InputStream in) throws IOException - { - this.in = in; - if (in == null) - { - throw new NullPointerException("InputStream == null"); - } - nativeData = nativeInit(); - if (nativeData == 0) - { - throw new OutOfMemoryError("Cannot initialize JNI liblzma object"); - } - } + public XZInputStream(InputStream in) throws IOException { + this.in = in; + if (in == null) { + throw new NullPointerException("InputStream == null"); + } + nativeData = nativeInit(); + if (nativeData == 0) { + throw new OutOfMemoryError("Cannot initialize JNI liblzma object"); + } + } - @Override - public int available() throws IOException - { - return 0; // Don't care - } + @Override + public int available() throws IOException { + return 0; // Don't care + } - @Override - public void close() throws IOException - { - synchronized (this) - { - if (nativeData != 0) - nativeClose(nativeData); - nativeData = 0; - if (in != null) - { - try { - in.close(); - } finally { - in = null; - } - } - } - } + @Override + public void close() throws IOException { + synchronized (this) { + if (nativeData != 0) + nativeClose(nativeData); + nativeData = 0; + if (in != null) { + try { + in.close(); + } finally { + in = null; + } + } + } + } - @Override - protected void finalize() throws IOException - { - try { - close(); - } finally { - try { - super.finalize(); - } catch (Throwable t) { - throw new AssertionError(t); - } - } - } + @Override + protected void finalize() throws IOException { + try { + close(); + } finally { + try { + super.finalize(); + } catch (Throwable t) { + throw new AssertionError(t); + } + } + } - @Override - public int read() throws IOException - { - return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF); - } + @Override + public int read() throws IOException { + return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF); + } - @Override - public int read(byte[] outBuf, int outOffset, int outCount) throws IOException - { - //Log.i("SDL", "XZInputStream.read: outOffset " + outOffset + " outCount " + outCount + " outBufEof " + outBufEof + - // " inOffset " + inOffset + " inAvailable " + inAvailable); - if (outBufEof) - return -1; - if (outCount <= 0) - return 0; + @Override + public int read(byte[] outBuf, int outOffset, int outCount) throws IOException { + //Log.i("SDL", "XZInputStream.read: outOffset " + outOffset + " outCount " + outCount + " outBufEof " + outBufEof + + // " inOffset " + inOffset + " inAvailable " + inAvailable); + if (outBufEof) + return -1; + if (outCount <= 0) + return 0; - int oldOutOffset = outOffset; + int oldOutOffset = outOffset; - if (inOffset >= inAvailable && inAvailable != -1) - { - inAvailable = in.read(inBuf, 0, inBuf.length); - inOffset = 0; - //Log.i("SDL", "XZInputStream.read: in.read: inOffset " + inOffset + " inAvailable " + inAvailable); - } + if (inOffset >= inAvailable && inAvailable != -1) { + inAvailable = in.read(inBuf, 0, inBuf.length); + inOffset = 0; + //Log.i("SDL", "XZInputStream.read: in.read: inOffset " + inOffset + " inAvailable " + inAvailable); + } - offsets[0] = inOffset; - offsets[1] = outOffset; - int ret = nativeRead(nativeData, inBuf, inAvailable, outBuf, outCount, offsets); - inOffset = offsets[0]; - outOffset = offsets[1]; - //Log.i("SDL", "XZInputStream.read: nativeRead: outOffset " + outOffset + " outCount " + outCount + " outBufEof " + outBufEof + - // " inOffset " + inOffset + " inAvailable " + inAvailable + " ret " + ret); + offsets[0] = inOffset; + offsets[1] = outOffset; + int ret = nativeRead(nativeData, inBuf, inAvailable, outBuf, outCount, offsets); + inOffset = offsets[0]; + outOffset = offsets[1]; + //Log.i("SDL", "XZInputStream.read: nativeRead: outOffset " + outOffset + " outCount " + outCount + " outBufEof " + outBufEof + + // " inOffset " + inOffset + " inAvailable " + inAvailable + " ret " + ret); - if (ret != 0) - { - if (ret == 1) - { - if (inOffset < inAvailable) - throw new IOException("Garbage at the end of LZMA stream"); - if (inAvailable != -1) - inAvailable = in.read(inBuf, 0, inBuf.length); - if (inAvailable != -1) - throw new IOException("Garbage at the end of LZMA stream"); - outBufEof = true; - } - else - { - throw new IOException("LZMA error " + ret); - } - } + if (ret != 0) { + if (ret == 1) { + if (inOffset < inAvailable) + throw new IOException("Garbage at the end of LZMA stream"); + if (inAvailable != -1) + inAvailable = in.read(inBuf, 0, inBuf.length); + if (inAvailable != -1) + throw new IOException("Garbage at the end of LZMA stream"); + outBufEof = true; + } else { + throw new IOException("LZMA error " + ret); + } + } - //Log.i("SDL", "XZInputStream.read: returning " + (outOffset - oldOutOffset)); - return outOffset - oldOutOffset; - } + //Log.i("SDL", "XZInputStream.read: returning " + (outOffset - oldOutOffset)); + return outOffset - oldOutOffset; + } - private native long nativeInit(); - private native void nativeClose(long nativeData); - private native int nativeRead(long nativeData, byte[] inBuf, int inAvailable, byte[] outBuf, int outCount, int[] offsets); + private native long nativeInit(); + + private native void nativeClose(long nativeData); + + private native int nativeRead(long nativeData, byte[] inBuf, int inAvailable, byte[] outBuf, int outCount, int[] offsets); } diff --git a/Xorg/src/main/java/io/neoterm/xorg/NeoXorgViewClient.java b/Xorg/src/main/java/io/neoterm/xorg/NeoXorgViewClient.java index 2a8bb34..b4215ea 100644 --- a/Xorg/src/main/java/io/neoterm/xorg/NeoXorgViewClient.java +++ b/Xorg/src/main/java/io/neoterm/xorg/NeoXorgViewClient.java @@ -3,7 +3,6 @@ package io.neoterm.xorg; import android.content.Context; import android.view.Window; import android.view.WindowManager; - import io.neoterm.NeoGLView; /** @@ -11,35 +10,35 @@ import io.neoterm.NeoGLView; */ public interface NeoXorgViewClient { - Context getContext(); + Context getContext(); - boolean isKeyboardWithoutTextInputShown(); + boolean isKeyboardWithoutTextInputShown(); - void showScreenKeyboardWithoutTextInputField(int flags); + void showScreenKeyboardWithoutTextInputField(int flags); - void setScreenKeyboardHintMessage(String hideMessage); + void setScreenKeyboardHintMessage(String hideMessage); - boolean isScreenKeyboardShown(); + boolean isScreenKeyboardShown(); - void showScreenKeyboard(String message); + void showScreenKeyboard(String message); - void hideScreenKeyboard(); + void hideScreenKeyboard(); - void runOnUiThread(Runnable runnable); + void runOnUiThread(Runnable runnable); - void updateScreenOrientation(); + void updateScreenOrientation(); - void initScreenOrientation(); + void initScreenOrientation(); - boolean isRunningOnOUYA(); + boolean isRunningOnOUYA(); - NeoGLView getGLView(); + NeoGLView getGLView(); - Window getWindow(); + Window getWindow(); - WindowManager getWindowManager(); + WindowManager getWindowManager(); - void setSystemMousePointerVisible(int visible); + void setSystemMousePointerVisible(int visible); - boolean isPaused(); + boolean isPaused(); } diff --git a/Xorg/src/main/res/values-zh/strings.xml b/Xorg/src/main/res/values-zh/strings.xml index 8446f77..868ad70 100644 --- a/Xorg/src/main/res/values-zh/strings.xml +++ b/Xorg/src/main/res/values-zh/strings.xml @@ -1,202 +1,204 @@ - 初始化中 - 正在下载数据,请稍候 + 初始化中 + 正在下载数据,请稍候 - 设备配置 - 更改设备配置 + 设备配置 + 更改设备配置 - 没有需要下载的内容 - 正在连接到 %s - 连接到 %s 失败 - %s 连接出错 - 正在从 %s 下载数据 - 从 %s 下载数据时出错 - 写入到 %s 时出错 - %1$.0f%% 已完成: 文件 %2$s - 已完成 + 没有需要下载的内容 + 正在连接到 %s + 连接到 %s 失败 + %s 连接出错 + 正在从 %s 下载数据 + 从 %s 下载数据时出错 + 写入到 %s 时出错 + %1$.0f%% 已完成: 文件 %2$s + 已完成 - 内部储存 - %d MB 空闲 - SD卡储存 - %d MB 空闲 - 自定义目录 - 命令行参数,每行一个参数 - 数据文件安装位置 - 下载 - 下载 - 完成 - 取消 + 内部储存 - %d MB 空闲 + SD卡储存 - %d MB 空闲 + 自定义目录 + 命令行参数,每行一个参数 + 数据文件安装位置 + 下载 + 下载 + 完成 + 取消 - 箭头 / 操纵杆 / 方向键 - 轨迹球 - 加速度计 - 只使用触屏 - 您的设备有哪些导航键? + 箭头 / 操纵杆 / 方向键 + 轨迹球 + 加速度计 + 只使用触屏 + 您的设备有哪些导航键? - 附加控制 - 屏幕键盘 - 加速度计 + 附加控制 + 屏幕键盘 + 加速度计 - 屏幕键盘大小 - 按钮大小 - - - - 微小 - 自定义 - 屏幕键盘主题 - %1$s by %2$s - 屏幕键盘透明度 - 隐形 - 半隐形 - 透明 - 半透明 - 不透明 + 屏幕键盘大小 + 按钮大小 + + + + 微小 + 自定义 + 屏幕键盘主题 + %1$s by %2$s + 屏幕键盘透明度 + 隐形 + 半隐形 + 透明 + 半透明 + 不透明 - 无阻碍 - - - - 轨迹球阻碍 + 无阻碍 + + + + 轨迹球阻碍 - 非常快 - - - - 非常慢 - 加速度计灵敏度 + 非常快 + + + + 非常慢 + 加速度计灵敏度 - Floating - 在应用程序启动时修复 - Fixed to table desk orientation - 加速度计中心位置 + Floating + 在应用程序启动时修复 + Fixed to table desk orientation + 加速度计中心位置 - 鼠标模拟 - 单击鼠标右键 - 菜单键 - 物理按键 - 双指触摸 - 使用按压力度 - 禁用鼠标右键 + 鼠标模拟 + 单击鼠标右键 + 菜单键 + 物理按键 + 双指触摸 + 使用按压力度 + 禁用鼠标右键 - 鼠标左键单击 - 正常 - 触摸靠近鼠标光标 - 双指触摸 - 使用按压力度 - 轨迹球点击 / 操纵杆中心 - 长按一个点 - 点击 - 点击或长按 - 长按超时 - 0.3 秒 - 0.5 秒 - 0.7 秒 - 1 秒 - 1.5 秒 - 左键点击和轨迹球点击 / 操纵杆中心 + 鼠标左键单击 + 正常 + 触摸靠近鼠标光标 + 双指触摸 + 使用按压力度 + 轨迹球点击 / 操纵杆中心 + 长按一个点 + 点击 + 点击或长按 + 长按超时 + 0.3 秒 + 0.5 秒 + 0.7 秒 + 1 秒 + 1.5 秒 + 左键点击和轨迹球点击 / 操纵杆中心 - 高级功能 - 保持4:3的屏幕宽高比 - 在单独的窗口中显示屏幕 - 屏幕放大镜 - 使用操纵杆或轨迹球移动鼠标 - 使用操纵杆移动鼠标时的速度 - 使用操纵杆加速移动鼠标 - 鼠标相对移动(笔记本模式) - 鼠标相对移动速度 - 鼠标相对移动加速 - 过滤指针/手指的抖动 - 用陀螺仪控制鼠标移动 - 陀螺仪灵敏度 - 手指抖动 - 每一帧的多点触摸事件 + 高级功能 + 保持4:3的屏幕宽高比 + 在单独的窗口中显示屏幕 + 屏幕放大镜 + 使用操纵杆或轨迹球移动鼠标 + 使用操纵杆移动鼠标时的速度 + 使用操纵杆加速移动鼠标 + 鼠标相对移动(笔记本模式) + 鼠标相对移动速度 + 鼠标相对移动加速 + 过滤指针/手指的抖动 + 用陀螺仪控制鼠标移动 + 陀螺仪灵敏度 + 手指抖动 + 每一帧的多点触摸事件 - + - 校准触摸屏压力 - 请将手指滑过屏幕两秒钟 - 压力 %1$03d 半径 %2$03d + 校准触摸屏压力 + 请将手指滑过屏幕两秒钟 + 压力 %1$03d 半径 %2$03d - 非常小(较新的设备,延迟低) - - 中等 - 大(较老的设备,如果声音不稳定请选此项) - 音频缓冲大小 + 非常小(较新的设备,延迟低) + + 中等 + 大(较老的设备,如果声音不稳定请选此项) + 音频缓冲大小 - 映射物理按键 - 按下任意按键 除了HOME键和POWER键,如音量键 - 选择SDL按键 - 选择动作 - 显示所有按键 + 映射物理按键 + 按下任意按键 除了HOME键和POWER键,如音量键 + 选择SDL按键 + 选择动作 + 显示所有按键 - 映射屏幕控件 - 手柄 - 按钮 - 文本输入按钮 - 双指手势 - 双指手势灵敏度 - 双指放大 - 双指缩小 - 双指向左旋转 - 双指向右旋转 + 映射屏幕控件 + 手柄 + 按钮 + 文本输入按钮 + 双指手势 + 双指手势灵敏度 + 双指放大 + 双指缩小 + 双指向左旋转 + 双指向右旋转 - 自定义屏幕键盘布局 - 按返回键结束,在空白区域滑动调整按钮大小 - 浮动操纵杆 + 自定义屏幕键盘布局 + 按返回键结束,在空白区域滑动调整按钮大小 + 浮动操纵杆 - 校准触摸屏 - 触摸屏幕的所有边缘,按返回键结束 + 校准触摸屏 + 触摸屏幕的所有边缘,按返回键结束 - 视频选项 - 线性过滤 - 用单线程处理视频,可能会提高FPS,也可能使程序崩溃 - 切换横屏/竖屏 - 自动检测屏幕方向 - 24 bpp颜色深度 - 隐藏系统导航按钮/沉浸模式 - 电视边框 + 视频选项 + 线性过滤 + 用单线程处理视频,可能会提高FPS,也可能使程序崩溃 + 切换横屏/竖屏 + 自动检测屏幕方向 + 24 bpp颜色深度 + 隐藏系统导航按钮/沉浸模式 + 电视边框 - 点击开始输入,按返回键结束 + 点击开始输入,按返回键结束 - 鼠标仿真模式 - 显示仿真鼠标的大小 - 桌面版,无仿真 - 大(适用于平板电脑) - 小,放大镜 - 小,触摸模式 - 很小 - 很小,触摸模式 + 鼠标仿真模式 + 显示仿真鼠标的大小 + 桌面版,无仿真 + 大(适用于平板电脑) + 小,放大镜 + 小,触摸模式 + 很小 + 很小,触摸模式 - 显示更多选项 + 显示更多选项 - 检测到鼠标硬件,禁用鼠标仿真 + 检测到鼠标硬件,禁用鼠标仿真 - 没有足够的 RAM - 本程序需要 %1$d Mb 的RAM,您的设备有 %2$d Mb - 忽略 + 没有足够的 RAM + 本程序需要 %1$d Mb 的RAM,您的设备有 %2$d Mb + 忽略 - 校准陀螺仪 - 将您的设备放在水平表面上 - 您的设备没有陀螺仪 + 校准陀螺仪 + 将您的设备放在水平表面上 + 您的设备没有陀螺仪 - 将所有配置重置为默认值 - 是否将所有选项重置为默认值? + 将所有配置重置为默认值 + 是否将所有选项重置为默认值? - 是否取消数据下载? - 您可以稍后恢复它,数据不会被下载两次。 - - + 是否取消数据下载? + 您可以稍后恢复它,数据不会被下载两次。 + + - - 无法登录,请检查您的网络连接,然后重试。 - 应用程序配置不正确。请检查包名和签名证书是否与开发者控制台中创建的客户端ID一致。此外,如果应用程序尚未发布,请检查您的帐户是否为测试人员帐户。详细信息,请参阅日志。 - 许可证检查失败。 - 未知错误。 - 正在访问网络,请稍候 + + 无法登录,请检查您的网络连接,然后重试。 + + 应用程序配置不正确。请检查包名和签名证书是否与开发者控制台中创建的客户端ID一致。此外,如果应用程序尚未发布,请检查您的帐户是否为测试人员帐户。详细信息,请参阅日志。 + + 许可证检查失败。 + 未知错误。 + 正在访问网络,请稍候 - 重新启动中,请稍候。 + 重新启动中,请稍候。 - %s 正在运行中 - 停止 + %s 正在运行中 + 停止 diff --git a/Xorg/src/main/res/values/dimen.xml b/Xorg/src/main/res/values/dimen.xml index 7043073..8b7cc0e 100644 --- a/Xorg/src/main/res/values/dimen.xml +++ b/Xorg/src/main/res/values/dimen.xml @@ -1,4 +1,4 @@ - 0dp - 0dp + 0dp + 0dp diff --git a/Xorg/src/main/res/values/ids.xml b/Xorg/src/main/res/values/ids.xml index e9a2c19..1c4617c 100644 --- a/Xorg/src/main/res/values/ids.xml +++ b/Xorg/src/main/res/values/ids.xml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/Xorg/src/main/res/values/strings.xml b/Xorg/src/main/res/values/strings.xml index ec6887f..b366d5e 100644 --- a/Xorg/src/main/res/values/strings.xml +++ b/Xorg/src/main/res/values/strings.xml @@ -2,205 +2,211 @@ - Initializing - Please wait while data is being downloaded + Initializing + Please wait while data is being downloaded - Device configuration - Change device configuration + Device configuration + Change device configuration - No need to download - Connecting to %s - Failed connecting to %s - Error connecting to %s - Downloading data from %s - Error downloading data from %s - Error writing to %s - %1$.0f%% done: file %2$s - Finished + No need to download + Connecting to %s + Failed connecting to %s + Error connecting to %s + Downloading data from %s + Error downloading data from %s + Error writing to %s + %1$.0f%% done: file %2$s + Finished - Internal storage - %d MB free - SD card storage - %d MB free - Specify directory - Command line parameters, one argument per line - Data installation location - Downloads - Downloads - OK - Cancel + Internal storage - %d MB free + SD card storage - %d MB free + Specify directory + Command line parameters, one argument per line + Data installation location + Downloads + Downloads + OK + Cancel - Arrows / joystick / dpad - Trackball - Accelerometer - Touchscreen only - What kind of navigation keys does your device have? + Arrows / joystick / dpad + Trackball + Accelerometer + Touchscreen only + What kind of navigation keys does your device have? - Additional controls - On-screen screenKeyboard - Accelerometer + Additional controls + On-screen screenKeyboard + Accelerometer - On-screen screenKeyboard size - Size of button images - Large - Medium - Small - Tiny - Custom - On-screen screenKeyboard theme - %1$s by %2$s - On-screen screenKeyboard transparency - Invisible - Almost invisible - Transparent - Semi-transparent - Non-transparent + On-screen screenKeyboard size + Size of button images + Large + Medium + Small + Tiny + Custom + On-screen screenKeyboard theme + %1$s by %2$s + On-screen screenKeyboard transparency + Invisible + Almost invisible + Transparent + Semi-transparent + Non-transparent - No dampening - Fast - Medium - Slow - Trackball dampening + No dampening + Fast + Medium + Slow + Trackball dampening - Very fast - Fast - Medium - Slow - Very slow - Accelerometer sensitivity + Very fast + Fast + Medium + Slow + Very slow + Accelerometer sensitivity - Floating - Fixed when application starts - Fixed to table desk orientation - Accelerometer center position + Floating + Fixed when application starts + Fixed to table desk orientation + Accelerometer center position - Mouse emulation - Right mouse click - Menu key - Physical key - Touch screen with second finger - Touch screen with force - Disable right mouse click + Mouse emulation + Right mouse click + Menu key + Physical key + Touch screen with second finger + Touch screen with force + Disable right mouse click - Left mouse click - Normal - Touch near mouse cursor - Touch screen with second finger - Touch screen with force - Trackball click / joystick center - Hold at the same spot - Tap - Tap or hold - Holding timeout - 0.3 sec - 0.5 sec - 0.7 sec - 1 sec - 1.5 sec - Left mouse click with trackball / joystick center + Left mouse click + Normal + Touch near mouse cursor + Touch screen with second finger + Touch screen with force + Trackball click / joystick center + Hold at the same spot + Tap + Tap or hold + Holding timeout + 0.3 sec + 0.5 sec + 0.7 sec + 1 sec + 1.5 sec + Left mouse click with trackball / joystick center - Advanced features - Keep 4:3 screen aspect ratio - Show screen under finger in separate window - On-screen magnifying glass - Move mouse with joystick or trackball - Move mouse with joystick speed - Move mouse with joystick acceleration - Relative mouse movement (laptop mode) - Relative mouse movement speed - Relative mouse movement acceleration - Filter jitter for stylus/finger hover - Control mouse with gyroscope - Gyroscope sensitivity - Finger hover - Multiple touch events per video frame + Advanced features + Keep 4:3 screen aspect ratio + Show screen under finger in separate window + On-screen magnifying glass + Move mouse with joystick or trackball + Move mouse with joystick speed + Move mouse with joystick acceleration + Relative mouse movement (laptop mode) + Relative mouse movement speed + Relative mouse movement acceleration + Filter jitter for stylus/finger hover + Control mouse with gyroscope + Gyroscope sensitivity + Finger hover + Multiple touch events per video frame - None + None - Calibrate touchscreen pressure - Please slide finger across the screen for two seconds - Pressure %1$03d radius %2$03d + Calibrate touchscreen pressure + Please slide finger across the screen for two seconds + Pressure %1$03d radius %2$03d - Very small (fast devices, less lag) - Small - Medium - Large (older devices, if sound is choppy) - Size of audio buffer + Very small (fast devices, less lag) + Small + Medium + Large (older devices, if sound is choppy) + Size of audio buffer - Remap physical keys - Press any key except HOME and POWER, you may use volume keys - Select SDL keycode - Select action - Show all keycodes + Remap physical keys + Press any key except HOME and POWER, you may use volume keys + Select SDL keycode + Select action + Show all keycodes - Remap on-screen controls - Joystick - Button - Text input button - Two-finger screen gestures - Two-finger screen gestures sensitivity - Zoom in two-finger gesture - Zoom out two-finger gesture - Rotate left two-finger gesture - Rotate right two-finger gesture + Remap on-screen controls + Joystick + Button + Text input button + Two-finger screen gestures + Two-finger screen gestures sensitivity + Zoom in two-finger gesture + Zoom out two-finger gesture + Rotate left two-finger gesture + Rotate right two-finger gesture - Customize on-screen screenKeyboard layout - Press BACK when done. Resize buttons by sliding on empty space. - Floating joystick + Customize on-screen screenKeyboard layout + Press BACK when done. Resize buttons by sliding on empty space. + Floating joystick - Calibrate touchscreen - Touch all edges of the screen, press BACK when done + Calibrate touchscreen + Touch all edges of the screen, press BACK when done - Video settings - Linear video filtering - Separate thread for video, it can increase FPS, it also can crash the app - Portrait/vertical screen orientation - Auto-detect screen orientation - 24 bpp screen color depth - Hide system navigation buttons / immersive mode - TV borders + Video settings + Linear video filtering + Separate thread for video, it can increase FPS, it also can crash the app + + Portrait/vertical screen orientation + Auto-detect screen orientation + 24 bpp screen color depth + Hide system navigation buttons / immersive mode + TV borders - Tap to start typing, press Back when done + Tap to start typing, press Back when done - Mouse emulation mode - Display size for mouse emulation - Desktop, no emulation - Large (tablets) - Small, magnifying glass - Small, touchpad mode - Tiny - Tiny, touchpad mode + Mouse emulation mode + Display size for mouse emulation + Desktop, no emulation + Large (tablets) + Small, magnifying glass + Small, touchpad mode + Tiny + Tiny, touchpad mode - Show more options + Show more options - Hardware mouse detected, disabling mouse emulation + Hardware mouse detected, disabling mouse emulation - Not enough RAM - This app needs %1$d Mb RAM, your device has %2$d Mb - Ignore + Not enough RAM + This app needs %1$d Mb RAM, your device has %2$d Mb + Ignore - Calibrate gyroscope - Put your device on a flat surface - Your device does not have gyroscope + Calibrate gyroscope + Put your device on a flat surface + Your device does not have gyroscope - Reset config to defaults - Reset all options to default values? + Reset config to defaults + Reset all options to default values? - Cancel data downloading? - You can resume it later, the data will not be downloaded twice. - Yes - No + Cancel data downloading? + You can resume it later, the data will not be downloaded twice. + Yes + No - - Failed to sign in. Please check your network connection and try again. - The application is incorrectly configured. Check that the package name and signing certificate match the client ID created in Developer Console. Also, if the application is not yet published, check that the account you are trying to sign in with is listed as a tester account. See logs for more information. - License check failed. - Unknown error. - Accessing network, please wait + + Failed to sign in. Please check your network connection and try again. + + The application is incorrectly configured. Check that the package name + and signing certificate match the client ID created in Developer Console. Also, if the application is not yet + published, check that the account you are trying to sign in with is listed as a tester account. See logs for + more information. + + License check failed. + Unknown error. + Accessing network, please wait - ==GOOGLEPLAYGAMESERVICES_APP_ID== + ==GOOGLEPLAYGAMESERVICES_APP_ID== - Restarting, please wait. + Restarting, please wait. - %s is running - Stop + %s is running + Stop diff --git a/Xorg/src/main/res/xml/amiga.xml b/Xorg/src/main/res/xml/amiga.xml index c67c0e5..48f7ef9 100644 --- a/Xorg/src/main/res/xml/amiga.xml +++ b/Xorg/src/main/res/xml/amiga.xml @@ -1,49 +1,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xorg/src/main/res/xml/amiga_alt.xml b/Xorg/src/main/res/xml/amiga_alt.xml index 880744c..7a6d45f 100644 --- a/Xorg/src/main/res/xml/amiga_alt.xml +++ b/Xorg/src/main/res/xml/amiga_alt.xml @@ -1,57 +1,57 @@ + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="10%p"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xorg/src/main/res/xml/amiga_alt_shift.xml b/Xorg/src/main/res/xml/amiga_alt_shift.xml index c92347f..d56303a 100644 --- a/Xorg/src/main/res/xml/amiga_alt_shift.xml +++ b/Xorg/src/main/res/xml/amiga_alt_shift.xml @@ -1,57 +1,57 @@ + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="10%p"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xorg/src/main/res/xml/amiga_old.xml b/Xorg/src/main/res/xml/amiga_old.xml index cb52255..9f31787 100644 --- a/Xorg/src/main/res/xml/amiga_old.xml +++ b/Xorg/src/main/res/xml/amiga_old.xml @@ -1,90 +1,90 @@ - - - - - - - - - - - - - - + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="10%p"> + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xorg/src/main/res/xml/amiga_shift.xml b/Xorg/src/main/res/xml/amiga_shift.xml index 52b804a..61f3642 100644 --- a/Xorg/src/main/res/xml/amiga_shift.xml +++ b/Xorg/src/main/res/xml/amiga_shift.xml @@ -1,53 +1,55 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="10%p"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xorg/src/main/res/xml/atari800.xml b/Xorg/src/main/res/xml/atari800.xml index c591efc..c70124d 100644 --- a/Xorg/src/main/res/xml/atari800.xml +++ b/Xorg/src/main/res/xml/atari800.xml @@ -1,85 +1,85 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="10%p"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xorg/src/main/res/xml/c64.xml b/Xorg/src/main/res/xml/c64.xml index 130babb..0bb8e85 100644 --- a/Xorg/src/main/res/xml/c64.xml +++ b/Xorg/src/main/res/xml/c64.xml @@ -1,85 +1,85 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="10%p"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xorg/src/main/res/xml/qwerty.xml b/Xorg/src/main/res/xml/qwerty.xml index 6d9dddc..b682246 100644 --- a/Xorg/src/main/res/xml/qwerty.xml +++ b/Xorg/src/main/res/xml/qwerty.xml @@ -1,53 +1,55 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="10%p"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xorg/src/main/res/xml/qwerty_alt.xml b/Xorg/src/main/res/xml/qwerty_alt.xml index 2bbc748..c0ffde9 100644 --- a/Xorg/src/main/res/xml/qwerty_alt.xml +++ b/Xorg/src/main/res/xml/qwerty_alt.xml @@ -1,57 +1,57 @@ + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="10%p"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xorg/src/main/res/xml/qwerty_alt_shift.xml b/Xorg/src/main/res/xml/qwerty_alt_shift.xml index a2db775..a7bbb7a 100644 --- a/Xorg/src/main/res/xml/qwerty_alt_shift.xml +++ b/Xorg/src/main/res/xml/qwerty_alt_shift.xml @@ -1,57 +1,57 @@ + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="10%p"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xorg/src/main/res/xml/qwerty_shift.xml b/Xorg/src/main/res/xml/qwerty_shift.xml index b54bc00..106c6d8 100644 --- a/Xorg/src/main/res/xml/qwerty_shift.xml +++ b/Xorg/src/main/res/xml/qwerty_shift.xml @@ -1,53 +1,55 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="10%p"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xorg/src/test/java/io/neoterm/ExampleUnitTest.java b/Xorg/src/test/java/io/neoterm/ExampleUnitTest.java index e31438d..e92732c 100644 --- a/Xorg/src/test/java/io/neoterm/ExampleUnitTest.java +++ b/Xorg/src/test/java/io/neoterm/ExampleUnitTest.java @@ -2,7 +2,7 @@ package io.neoterm; import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** * Example local unit test, which will execute on the development machine (host). @@ -10,8 +10,8 @@ import static org.junit.Assert.*; * @see Testing documentation */ public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } } \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 73a5808..0898ec9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,70 +2,70 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion rootProject.ext.android.COMPILE_SDK_VERSION + compileSdkVersion rootProject.ext.android.COMPILE_SDK_VERSION - defaultConfig { - applicationId "io.neoterm" - minSdkVersion rootProject.ext.android.MIN_SDK_VERSION - targetSdkVersion rootProject.ext.android.TARGET_SDK_VERSION - versionCode 37 - versionName "2.0.5" - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' - resConfigs "zh-rCN", "zh-rTW" - externalNativeBuild { - cmake { - cppFlags "-std=c++11" - abiFilters 'arm64-v8a' - } - } - sourceSets { - main { - jniLibs.srcDirs = ['src/main/jniLibs'] - } - } - } - buildTypes { - release { - minifyEnabled true - zipAlignEnabled true - shrinkResources true - } - } + defaultConfig { + applicationId "io.neoterm" + minSdkVersion rootProject.ext.android.MIN_SDK_VERSION + targetSdkVersion rootProject.ext.android.TARGET_SDK_VERSION + versionCode 37 + versionName "2.0.5" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + resConfigs "zh-rCN", "zh-rTW" externalNativeBuild { - cmake { - path "CMakeLists.txt" - } + cmake { + cppFlags "-std=c++11" + abiFilters 'arm64-v8a' + } } - lintOptions { - abortOnError false - checkReleaseBuilds false + sourceSets { + main { + jniLibs.srcDirs = ['src/main/jniLibs'] + } } - compileOptions { - targetCompatibility 1.8 - sourceCompatibility 1.8 + } + buildTypes { + release { + minifyEnabled true + zipAlignEnabled true + shrinkResources true } + } + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } + compileOptions { + targetCompatibility 1.8 + sourceCompatibility 1.8 + } } dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - testImplementation rootProject.ext.deps["junit"] - androidTestImplementation project(path: ':NeoLang') + implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation rootProject.ext.deps["junit"] + androidTestImplementation project(path: ':NeoLang') - implementation rootProject.ext.deps["kotlin-stdlib"] + implementation rootProject.ext.deps["kotlin-stdlib"] - implementation 'org.greenrobot:eventbus:3.0.0' - implementation 'com.github.wrdlbrnft:modular-adapter:0.2.0.6' - implementation 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.19' - implementation 'com.simplecityapps:recyclerview-fastscroll:1.0.16' - implementation 'de.psdev.licensesdialog:licensesdialog:1.8.3' - implementation 'com.github.GrenderG:Color-O-Matic:1.1.5' - implementation 'androidx.annotation:annotation:1.2.0' - implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.appcompat:appcompat-resources:1.2.0' + implementation 'org.greenrobot:eventbus:3.0.0' + implementation 'com.github.wrdlbrnft:modular-adapter:0.2.0.6' + implementation 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.19' + implementation 'com.simplecityapps:recyclerview-fastscroll:1.0.16' + implementation 'de.psdev.licensesdialog:licensesdialog:1.8.3' + implementation 'com.github.GrenderG:Color-O-Matic:1.1.5' + implementation 'androidx.annotation:annotation:1.2.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat-resources:1.2.0' - implementation project(':chrome-tabs') - implementation project(':NeoLang') - implementation project(':NeoTermBridge') - implementation project(':Xorg') + implementation project(':chrome-tabs') + implementation project(':NeoLang') + implementation project(':NeoTermBridge') + implementation project(':Xorg') } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 25510e3..aa35c5a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,194 +1,194 @@ + package="io.neoterm"> - - + + - - - - - - - + + + + + + + - - - - + + + + - - - - - - + + + + + + - - + + - - - + + + - - - - + + + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + - - - - - + + + + + - - + + - - - - - + + + + + - - + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - + + + - + - + - - + + \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/App.kt b/app/src/main/java/io/neoterm/App.kt index 0db4a5b..08612af 100644 --- a/app/src/main/java/io/neoterm/App.kt +++ b/app/src/main/java/io/neoterm/App.kt @@ -17,60 +17,60 @@ import io.neoterm.utils.CrashHandler * @author kiva */ class App : Application() { - override fun onCreate() { - super.onCreate() - app = this - NeoPreference.init(this) - CrashHandler.init() - NeoInitializer.init(this) + override fun onCreate() { + super.onCreate() + app = this + NeoPreference.init(this) + CrashHandler.init() + NeoInitializer.init(this) + } + + fun errorDialog(context: Context, message: Int, dismissCallback: (() -> Unit)?) { + errorDialog(context, getString(message), dismissCallback) + } + + fun errorDialog(context: Context, message: String, dismissCallback: (() -> Unit)?) { + AlertDialog.Builder(context) + .setTitle(R.string.error) + .setMessage(message) + .setNegativeButton(android.R.string.no, null) + .setPositiveButton(R.string.show_help) { _, _ -> + openHelpLink() + } + .setOnDismissListener { + dismissCallback?.invoke() + } + .show() + } + + fun openHelpLink() { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://neoterm.gitbooks.io/neoterm-wiki/content/")) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + } + + fun easterEgg(context: Context, message: String) { + val happyCount = NeoPreference.loadInt(NeoPreference.KEY_HAPPY_EGG, 0) + 1 + NeoPreference.store(NeoPreference.KEY_HAPPY_EGG, happyCount) + + val trigger = NeoPreference.VALUE_HAPPY_EGG_TRIGGER + + if (happyCount == trigger / 2) { + @SuppressLint("ShowToast") + val toast = Toast.makeText(this, message, Toast.LENGTH_LONG) + toast.setGravity(Gravity.CENTER, 0, 0) + toast.show() + } else if (happyCount > trigger) { + NeoPreference.store(NeoPreference.KEY_HAPPY_EGG, 0) + context.startActivity(Intent(context, BonusActivity::class.java)) } + } - fun errorDialog(context: Context, message: Int, dismissCallback: (() -> Unit)?) { - errorDialog(context, getString(message), dismissCallback) - } - - fun errorDialog(context: Context, message: String, dismissCallback: (() -> Unit)?) { - AlertDialog.Builder(context) - .setTitle(R.string.error) - .setMessage(message) - .setNegativeButton(android.R.string.no, null) - .setPositiveButton(R.string.show_help) { _, _ -> - openHelpLink() - } - .setOnDismissListener { - dismissCallback?.invoke() - } - .show() - } - - fun openHelpLink() { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://neoterm.gitbooks.io/neoterm-wiki/content/")) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - } - - fun easterEgg(context: Context, message: String) { - val happyCount = NeoPreference.loadInt(NeoPreference.KEY_HAPPY_EGG, 0) + 1 - NeoPreference.store(NeoPreference.KEY_HAPPY_EGG, happyCount) - - val trigger = NeoPreference.VALUE_HAPPY_EGG_TRIGGER - - if (happyCount == trigger / 2) { - @SuppressLint("ShowToast") - val toast = Toast.makeText(this, message, Toast.LENGTH_LONG) - toast.setGravity(Gravity.CENTER, 0, 0) - toast.show() - } else if (happyCount > trigger) { - NeoPreference.store(NeoPreference.KEY_HAPPY_EGG, 0) - context.startActivity(Intent(context, BonusActivity::class.java)) - } - } - - companion object { - private var app: App? = null - - fun get(): App { - return app!! - } + companion object { + private var app: App? = null + + fun get(): App { + return app!! } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/backend/ByteQueue.java b/app/src/main/java/io/neoterm/backend/ByteQueue.java index d361249..ffa8251 100755 --- a/app/src/main/java/io/neoterm/backend/ByteQueue.java +++ b/app/src/main/java/io/neoterm/backend/ByteQueue.java @@ -1,108 +1,110 @@ package io.neoterm.backend; -/** A circular byte buffer allowing one producer and one consumer thread. */ +/** + * A circular byte buffer allowing one producer and one consumer thread. + */ final class ByteQueue { - private final byte[] mBuffer; - private int mHead; - private int mStoredBytes; - private boolean mOpen = true; + private final byte[] mBuffer; + private int mHead; + private int mStoredBytes; + private boolean mOpen = true; - public ByteQueue(int size) { - mBuffer = new byte[size]; + public ByteQueue(int size) { + mBuffer = new byte[size]; + } + + public synchronized void close() { + mOpen = false; + notify(); + } + + public synchronized int read(byte[] buffer, boolean block) { + while (mStoredBytes == 0 && mOpen) { + if (block) { + try { + wait(); + } catch (InterruptedException e) { + // Ignore. + } + } else { + return 0; + } + } + if (!mOpen) return -1; + + int totalRead = 0; + int bufferLength = mBuffer.length; + boolean wasFull = bufferLength == mStoredBytes; + int length = buffer.length; + int offset = 0; + while (length > 0 && mStoredBytes > 0) { + int oneRun = Math.min(bufferLength - mHead, mStoredBytes); + int bytesToCopy = Math.min(length, oneRun); + System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy); + mHead += bytesToCopy; + if (mHead >= bufferLength) mHead = 0; + mStoredBytes -= bytesToCopy; + length -= bytesToCopy; + offset += bytesToCopy; + totalRead += bytesToCopy; + } + if (wasFull) notify(); + return totalRead; + } + + /** + * Attempt to write the specified portion of the provided buffer to the queue. + *

+ * Returns whether the output was totally written, false if it was closed before. + */ + public boolean write(byte[] buffer, int offset, int lengthToWrite) { + if (lengthToWrite + offset > buffer.length) { + throw new IllegalArgumentException("length + offset > buffer.length"); + } else if (lengthToWrite <= 0) { + throw new IllegalArgumentException("length <= 0"); } - public synchronized void close() { - mOpen = false; - notify(); - } - - public synchronized int read(byte[] buffer, boolean block) { - while (mStoredBytes == 0 && mOpen) { - if (block) { - try { - wait(); - } catch (InterruptedException e) { - // Ignore. - } - } else { - return 0; - } - } - if (!mOpen) return -1; - - int totalRead = 0; - int bufferLength = mBuffer.length; - boolean wasFull = bufferLength == mStoredBytes; - int length = buffer.length; - int offset = 0; - while (length > 0 && mStoredBytes > 0) { - int oneRun = Math.min(bufferLength - mHead, mStoredBytes); - int bytesToCopy = Math.min(length, oneRun); - System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy); - mHead += bytesToCopy; - if (mHead >= bufferLength) mHead = 0; - mStoredBytes -= bytesToCopy; - length -= bytesToCopy; - offset += bytesToCopy; - totalRead += bytesToCopy; - } - if (wasFull) notify(); - return totalRead; - } - - /** - * Attempt to write the specified portion of the provided buffer to the queue. - *

- * Returns whether the output was totally written, false if it was closed before. - */ - public boolean write(byte[] buffer, int offset, int lengthToWrite) { - if (lengthToWrite + offset > buffer.length) { - throw new IllegalArgumentException("length + offset > buffer.length"); - } else if (lengthToWrite <= 0) { - throw new IllegalArgumentException("length <= 0"); - } - - final int bufferLength = mBuffer.length; - - synchronized (this) { - while (lengthToWrite > 0) { - while (bufferLength == mStoredBytes && mOpen) { - try { - wait(); - } catch (InterruptedException e) { - // Ignore. - } - } - if (!mOpen) return false; - final boolean wasEmpty = mStoredBytes == 0; - int bytesToWriteBeforeWaiting = Math.min(lengthToWrite, bufferLength - mStoredBytes); - lengthToWrite -= bytesToWriteBeforeWaiting; - - while (bytesToWriteBeforeWaiting > 0) { - int tail = mHead + mStoredBytes; - int oneRun; - if (tail >= bufferLength) { - // Buffer: [.............] - // ________________H_______T - // => - // Buffer: [.............] - // ___________T____H - // onRun= _____----_ - tail = tail - bufferLength; - oneRun = mHead - tail; - } else { - oneRun = bufferLength - tail; - } - int bytesToCopy = Math.min(oneRun, bytesToWriteBeforeWaiting); - System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy); - offset += bytesToCopy; - bytesToWriteBeforeWaiting -= bytesToCopy; - mStoredBytes += bytesToCopy; - } - if (wasEmpty) notify(); - } - } - return true; + final int bufferLength = mBuffer.length; + + synchronized (this) { + while (lengthToWrite > 0) { + while (bufferLength == mStoredBytes && mOpen) { + try { + wait(); + } catch (InterruptedException e) { + // Ignore. + } + } + if (!mOpen) return false; + final boolean wasEmpty = mStoredBytes == 0; + int bytesToWriteBeforeWaiting = Math.min(lengthToWrite, bufferLength - mStoredBytes); + lengthToWrite -= bytesToWriteBeforeWaiting; + + while (bytesToWriteBeforeWaiting > 0) { + int tail = mHead + mStoredBytes; + int oneRun; + if (tail >= bufferLength) { + // Buffer: [.............] + // ________________H_______T + // => + // Buffer: [.............] + // ___________T____H + // onRun= _____----_ + tail = tail - bufferLength; + oneRun = mHead - tail; + } else { + oneRun = bufferLength - tail; + } + int bytesToCopy = Math.min(oneRun, bytesToWriteBeforeWaiting); + System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy); + offset += bytesToCopy; + bytesToWriteBeforeWaiting -= bytesToCopy; + mStoredBytes += bytesToCopy; + } + if (wasEmpty) notify(); + } } + return true; + } } diff --git a/app/src/main/java/io/neoterm/backend/EmulatorDebug.java b/app/src/main/java/io/neoterm/backend/EmulatorDebug.java index 86f24c6..69433dc 100755 --- a/app/src/main/java/io/neoterm/backend/EmulatorDebug.java +++ b/app/src/main/java/io/neoterm/backend/EmulatorDebug.java @@ -4,7 +4,9 @@ import android.util.Log; public final class EmulatorDebug { - /** The tag to use with {@link Log}. */ - public static final String LOG_TAG = "NeoTerm-Emulator"; + /** + * The tag to use with {@link Log}. + */ + public static final String LOG_TAG = "NeoTerm-Emulator"; } diff --git a/app/src/main/java/io/neoterm/backend/JNI.java b/app/src/main/java/io/neoterm/backend/JNI.java index f154ed2..155cf94 100755 --- a/app/src/main/java/io/neoterm/backend/JNI.java +++ b/app/src/main/java/io/neoterm/backend/JNI.java @@ -5,37 +5,41 @@ package io.neoterm.backend; */ final class JNI { - static { - System.loadLibrary("neoterm"); - } + static { + System.loadLibrary("neoterm"); + } - /** - * Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the - * subprocess. - *

- * Callers are responsible for calling {@link #close(int)} on the returned file descriptor. - * - * @param cmd The command to execute - * @param cwd The current working directory for the executed command - * @param args An array of arguments to the command - * @param envVars An array of strings of the form "VAR=value" to be added to the environment of the process - * @param processId A one-element array to which the process ID of the started process will be written. - * @return the file descriptor resulting from opening /dev/ptmx master device. The sub process will have opened the - * slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr. - */ - public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns); + /** + * Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the + * subprocess. + *

+ * Callers are responsible for calling {@link #close(int)} on the returned file descriptor. + * + * @param cmd The command to execute + * @param cwd The current working directory for the executed command + * @param args An array of arguments to the command + * @param envVars An array of strings of the form "VAR=value" to be added to the environment of the process + * @param processId A one-element array to which the process ID of the started process will be written. + * @return the file descriptor resulting from opening /dev/ptmx master device. The sub process will have opened the + * slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr. + */ + public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns); - /** Set the window size for a given pty, which allows connected programs to learn how large their screen is. */ - public static native void setPtyWindowSize(int fd, int rows, int cols); + /** + * Set the window size for a given pty, which allows connected programs to learn how large their screen is. + */ + public static native void setPtyWindowSize(int fd, int rows, int cols); - /** - * Causes the calling thread to wait for the process associated with the receiver to finish executing. - * - * @return if >= 0, the exit status of the process. If < 0, the signal causing the process to stop negated. - */ - public static native int waitFor(int processId); + /** + * Causes the calling thread to wait for the process associated with the receiver to finish executing. + * + * @return if >= 0, the exit status of the process. If < 0, the signal causing the process to stop negated. + */ + public static native int waitFor(int processId); - /** Close a file descriptor through the close(2) system call. */ - public static native void close(int fileDescriptor); + /** + * Close a file descriptor through the close(2) system call. + */ + public static native void close(int fileDescriptor); } diff --git a/app/src/main/java/io/neoterm/backend/KeyHandler.java b/app/src/main/java/io/neoterm/backend/KeyHandler.java index 6898d95..4e334c9 100755 --- a/app/src/main/java/io/neoterm/backend/KeyHandler.java +++ b/app/src/main/java/io/neoterm/backend/KeyHandler.java @@ -3,311 +3,262 @@ package io.neoterm.backend; import java.util.HashMap; import java.util.Map; -import static android.view.KeyEvent.KEYCODE_BACK; -import static android.view.KeyEvent.KEYCODE_BREAK; -import static android.view.KeyEvent.KEYCODE_DEL; -import static android.view.KeyEvent.KEYCODE_DPAD_CENTER; -import static android.view.KeyEvent.KEYCODE_DPAD_DOWN; -import static android.view.KeyEvent.KEYCODE_DPAD_LEFT; -import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT; -import static android.view.KeyEvent.KEYCODE_DPAD_UP; -import static android.view.KeyEvent.KEYCODE_ENTER; -import static android.view.KeyEvent.KEYCODE_ESCAPE; -import static android.view.KeyEvent.KEYCODE_F1; -import static android.view.KeyEvent.KEYCODE_F10; -import static android.view.KeyEvent.KEYCODE_F11; -import static android.view.KeyEvent.KEYCODE_F12; -import static android.view.KeyEvent.KEYCODE_F2; -import static android.view.KeyEvent.KEYCODE_F3; -import static android.view.KeyEvent.KEYCODE_F4; -import static android.view.KeyEvent.KEYCODE_F5; -import static android.view.KeyEvent.KEYCODE_F6; -import static android.view.KeyEvent.KEYCODE_F7; -import static android.view.KeyEvent.KEYCODE_F8; -import static android.view.KeyEvent.KEYCODE_F9; -import static android.view.KeyEvent.KEYCODE_FORWARD_DEL; -import static android.view.KeyEvent.KEYCODE_INSERT; -import static android.view.KeyEvent.KEYCODE_MOVE_END; -import static android.view.KeyEvent.KEYCODE_MOVE_HOME; -import static android.view.KeyEvent.KEYCODE_NUMPAD_0; -import static android.view.KeyEvent.KEYCODE_NUMPAD_1; -import static android.view.KeyEvent.KEYCODE_NUMPAD_2; -import static android.view.KeyEvent.KEYCODE_NUMPAD_3; -import static android.view.KeyEvent.KEYCODE_NUMPAD_4; -import static android.view.KeyEvent.KEYCODE_NUMPAD_5; -import static android.view.KeyEvent.KEYCODE_NUMPAD_6; -import static android.view.KeyEvent.KEYCODE_NUMPAD_7; -import static android.view.KeyEvent.KEYCODE_NUMPAD_8; -import static android.view.KeyEvent.KEYCODE_NUMPAD_9; -import static android.view.KeyEvent.KEYCODE_NUMPAD_ADD; -import static android.view.KeyEvent.KEYCODE_NUMPAD_COMMA; -import static android.view.KeyEvent.KEYCODE_NUMPAD_DIVIDE; -import static android.view.KeyEvent.KEYCODE_NUMPAD_DOT; -import static android.view.KeyEvent.KEYCODE_NUMPAD_ENTER; -import static android.view.KeyEvent.KEYCODE_NUMPAD_EQUALS; -import static android.view.KeyEvent.KEYCODE_NUMPAD_MULTIPLY; -import static android.view.KeyEvent.KEYCODE_NUMPAD_SUBTRACT; -import static android.view.KeyEvent.KEYCODE_NUM_LOCK; -import static android.view.KeyEvent.KEYCODE_PAGE_DOWN; -import static android.view.KeyEvent.KEYCODE_PAGE_UP; -import static android.view.KeyEvent.KEYCODE_SPACE; -import static android.view.KeyEvent.KEYCODE_SYSRQ; -import static android.view.KeyEvent.KEYCODE_TAB; +import static android.view.KeyEvent.*; public final class KeyHandler { - public static final int KEYMOD_ALT = 0x80000000; - public static final int KEYMOD_CTRL = 0x40000000; - public static final int KEYMOD_SHIFT = 0x20000000; + public static final int KEYMOD_ALT = 0x80000000; + public static final int KEYMOD_CTRL = 0x40000000; + public static final int KEYMOD_SHIFT = 0x20000000; - private static final Map TERMCAP_TO_KEYCODE = new HashMap<>(); + private static final Map TERMCAP_TO_KEYCODE = new HashMap<>(); - static { - // terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html - // termcap: http://man7.org/linux/man-pages/man5/termcap.5.html - TERMCAP_TO_KEYCODE.put("%i", KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT); - TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_MOVE_HOME); // Shifted home - TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT); - TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key + static { + // terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html + // termcap: http://man7.org/linux/man-pages/man5/termcap.5.html + TERMCAP_TO_KEYCODE.put("%i", KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT); + TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_MOVE_HOME); // Shifted home + TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT); + TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key - TERMCAP_TO_KEYCODE.put("k1", KEYCODE_F1); - TERMCAP_TO_KEYCODE.put("k2", KEYCODE_F2); - TERMCAP_TO_KEYCODE.put("k3", KEYCODE_F3); - TERMCAP_TO_KEYCODE.put("k4", KEYCODE_F4); - TERMCAP_TO_KEYCODE.put("k5", KEYCODE_F5); - TERMCAP_TO_KEYCODE.put("k6", KEYCODE_F6); - TERMCAP_TO_KEYCODE.put("k7", KEYCODE_F7); - TERMCAP_TO_KEYCODE.put("k8", KEYCODE_F8); - TERMCAP_TO_KEYCODE.put("k9", KEYCODE_F9); - TERMCAP_TO_KEYCODE.put("k;", KEYCODE_F10); - TERMCAP_TO_KEYCODE.put("F1", KEYCODE_F11); - TERMCAP_TO_KEYCODE.put("F2", KEYCODE_F12); - TERMCAP_TO_KEYCODE.put("F3", KEYMOD_SHIFT | KEYCODE_F1); - TERMCAP_TO_KEYCODE.put("F4", KEYMOD_SHIFT | KEYCODE_F2); - TERMCAP_TO_KEYCODE.put("F5", KEYMOD_SHIFT | KEYCODE_F3); - TERMCAP_TO_KEYCODE.put("F6", KEYMOD_SHIFT | KEYCODE_F4); - TERMCAP_TO_KEYCODE.put("F7", KEYMOD_SHIFT | KEYCODE_F5); - TERMCAP_TO_KEYCODE.put("F8", KEYMOD_SHIFT | KEYCODE_F6); - TERMCAP_TO_KEYCODE.put("F9", KEYMOD_SHIFT | KEYCODE_F7); - TERMCAP_TO_KEYCODE.put("FA", KEYMOD_SHIFT | KEYCODE_F8); - TERMCAP_TO_KEYCODE.put("FB", KEYMOD_SHIFT | KEYCODE_F9); - TERMCAP_TO_KEYCODE.put("FC", KEYMOD_SHIFT | KEYCODE_F10); - TERMCAP_TO_KEYCODE.put("FD", KEYMOD_SHIFT | KEYCODE_F11); - TERMCAP_TO_KEYCODE.put("FE", KEYMOD_SHIFT | KEYCODE_F12); + TERMCAP_TO_KEYCODE.put("k1", KEYCODE_F1); + TERMCAP_TO_KEYCODE.put("k2", KEYCODE_F2); + TERMCAP_TO_KEYCODE.put("k3", KEYCODE_F3); + TERMCAP_TO_KEYCODE.put("k4", KEYCODE_F4); + TERMCAP_TO_KEYCODE.put("k5", KEYCODE_F5); + TERMCAP_TO_KEYCODE.put("k6", KEYCODE_F6); + TERMCAP_TO_KEYCODE.put("k7", KEYCODE_F7); + TERMCAP_TO_KEYCODE.put("k8", KEYCODE_F8); + TERMCAP_TO_KEYCODE.put("k9", KEYCODE_F9); + TERMCAP_TO_KEYCODE.put("k;", KEYCODE_F10); + TERMCAP_TO_KEYCODE.put("F1", KEYCODE_F11); + TERMCAP_TO_KEYCODE.put("F2", KEYCODE_F12); + TERMCAP_TO_KEYCODE.put("F3", KEYMOD_SHIFT | KEYCODE_F1); + TERMCAP_TO_KEYCODE.put("F4", KEYMOD_SHIFT | KEYCODE_F2); + TERMCAP_TO_KEYCODE.put("F5", KEYMOD_SHIFT | KEYCODE_F3); + TERMCAP_TO_KEYCODE.put("F6", KEYMOD_SHIFT | KEYCODE_F4); + TERMCAP_TO_KEYCODE.put("F7", KEYMOD_SHIFT | KEYCODE_F5); + TERMCAP_TO_KEYCODE.put("F8", KEYMOD_SHIFT | KEYCODE_F6); + TERMCAP_TO_KEYCODE.put("F9", KEYMOD_SHIFT | KEYCODE_F7); + TERMCAP_TO_KEYCODE.put("FA", KEYMOD_SHIFT | KEYCODE_F8); + TERMCAP_TO_KEYCODE.put("FB", KEYMOD_SHIFT | KEYCODE_F9); + TERMCAP_TO_KEYCODE.put("FC", KEYMOD_SHIFT | KEYCODE_F10); + TERMCAP_TO_KEYCODE.put("FD", KEYMOD_SHIFT | KEYCODE_F11); + TERMCAP_TO_KEYCODE.put("FE", KEYMOD_SHIFT | KEYCODE_F12); - TERMCAP_TO_KEYCODE.put("kb", KEYCODE_DEL); // backspace key + TERMCAP_TO_KEYCODE.put("kb", KEYCODE_DEL); // backspace key - TERMCAP_TO_KEYCODE.put("kd", KEYCODE_DPAD_DOWN); // terminfo=kcud1, down-arrow key - TERMCAP_TO_KEYCODE.put("kh", KEYCODE_MOVE_HOME); - TERMCAP_TO_KEYCODE.put("kl", KEYCODE_DPAD_LEFT); - TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT); + TERMCAP_TO_KEYCODE.put("kd", KEYCODE_DPAD_DOWN); // terminfo=kcud1, down-arrow key + TERMCAP_TO_KEYCODE.put("kh", KEYCODE_MOVE_HOME); + TERMCAP_TO_KEYCODE.put("kl", KEYCODE_DPAD_LEFT); + TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT); - // K1=Upper left of keypad: - // t_K1 keypad home key - // t_K3 keypad page-up key - // t_K4 keypad end key - // t_K5 keypad page-down key - TERMCAP_TO_KEYCODE.put("K1", KEYCODE_MOVE_HOME); - TERMCAP_TO_KEYCODE.put("K3", KEYCODE_PAGE_UP); - TERMCAP_TO_KEYCODE.put("K4", KEYCODE_MOVE_END); - TERMCAP_TO_KEYCODE.put("K5", KEYCODE_PAGE_DOWN); + // K1=Upper left of keypad: + // t_K1 keypad home key + // t_K3 keypad page-up key + // t_K4 keypad end key + // t_K5 keypad page-down key + TERMCAP_TO_KEYCODE.put("K1", KEYCODE_MOVE_HOME); + TERMCAP_TO_KEYCODE.put("K3", KEYCODE_PAGE_UP); + TERMCAP_TO_KEYCODE.put("K4", KEYCODE_MOVE_END); + TERMCAP_TO_KEYCODE.put("K5", KEYCODE_PAGE_DOWN); - TERMCAP_TO_KEYCODE.put("ku", KEYCODE_DPAD_UP); + TERMCAP_TO_KEYCODE.put("ku", KEYCODE_DPAD_UP); - TERMCAP_TO_KEYCODE.put("kB", KEYMOD_SHIFT | KEYCODE_TAB); // termcap=kB, terminfo=kcbt: Back-tab_switcher - TERMCAP_TO_KEYCODE.put("kD", KEYCODE_FORWARD_DEL); // terminfo=kdch1, delete-character key - TERMCAP_TO_KEYCODE.put("kDN", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // non-standard shifted arrow down - TERMCAP_TO_KEYCODE.put("kF", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // terminfo=kind, scroll-forward key - TERMCAP_TO_KEYCODE.put("kI", KEYCODE_INSERT); - TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_UP); - TERMCAP_TO_KEYCODE.put("kP", KEYCODE_PAGE_DOWN); - TERMCAP_TO_KEYCODE.put("kR", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // terminfo=kri, scroll-backward key - TERMCAP_TO_KEYCODE.put("kUP", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // non-standard shifted up + TERMCAP_TO_KEYCODE.put("kB", KEYMOD_SHIFT | KEYCODE_TAB); // termcap=kB, terminfo=kcbt: Back-tab_switcher + TERMCAP_TO_KEYCODE.put("kD", KEYCODE_FORWARD_DEL); // terminfo=kdch1, delete-character key + TERMCAP_TO_KEYCODE.put("kDN", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // non-standard shifted arrow down + TERMCAP_TO_KEYCODE.put("kF", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // terminfo=kind, scroll-forward key + TERMCAP_TO_KEYCODE.put("kI", KEYCODE_INSERT); + TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_UP); + TERMCAP_TO_KEYCODE.put("kP", KEYCODE_PAGE_DOWN); + TERMCAP_TO_KEYCODE.put("kR", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // terminfo=kri, scroll-backward key + TERMCAP_TO_KEYCODE.put("kUP", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // non-standard shifted up - TERMCAP_TO_KEYCODE.put("@7", KEYCODE_MOVE_END); - TERMCAP_TO_KEYCODE.put("@8", KEYCODE_NUMPAD_ENTER); + TERMCAP_TO_KEYCODE.put("@7", KEYCODE_MOVE_END); + TERMCAP_TO_KEYCODE.put("@8", KEYCODE_NUMPAD_ENTER); + } + + static String getCodeFromTermcap(String termcap, boolean cursorKeysApplication, boolean keypadApplication) { + Integer keyCodeAndMod = TERMCAP_TO_KEYCODE.get(termcap); + if (keyCodeAndMod == null) return null; + int keyCode = keyCodeAndMod; + int keyMod = 0; + if ((keyCode & KEYMOD_SHIFT) != 0) { + keyMod |= KEYMOD_SHIFT; + keyCode &= ~KEYMOD_SHIFT; + } + if ((keyCode & KEYMOD_CTRL) != 0) { + keyMod |= KEYMOD_CTRL; + keyCode &= ~KEYMOD_CTRL; + } + if ((keyCode & KEYMOD_ALT) != 0) { + keyMod |= KEYMOD_ALT; + keyCode &= ~KEYMOD_ALT; + } + return getCode(keyCode, keyMod, cursorKeysApplication, keypadApplication); + } + + public static String getCode(int keyCode, int keyMode, boolean cursorApp, boolean keypadApplication) { + switch (keyCode) { + case KEYCODE_DPAD_CENTER: + return "\015"; + + case KEYCODE_DPAD_UP: + return (keyMode == 0) ? (cursorApp ? "\033OA" : "\033[A") : transformForModifiers("\033[1", keyMode, 'A'); + case KEYCODE_DPAD_DOWN: + return (keyMode == 0) ? (cursorApp ? "\033OB" : "\033[B") : transformForModifiers("\033[1", keyMode, 'B'); + case KEYCODE_DPAD_RIGHT: + return (keyMode == 0) ? (cursorApp ? "\033OC" : "\033[C") : transformForModifiers("\033[1", keyMode, 'C'); + case KEYCODE_DPAD_LEFT: + return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D'); + + case KEYCODE_MOVE_HOME: + // Note that KEYCODE_HOME is handled by the system and never delivered to applications. + // On a Logitech k810 keyboard KEYCODE_MOVE_HOME is sent by FN+LeftArrow. + return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H'); + case KEYCODE_MOVE_END: + return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F'); + + // An xterm can send function keys F1 to F4 in two modes: vt100 compatible or + // not. Because Vim may not know what the xterm is sending, both types of keys + // are recognized. The same happens for the and keys. + // normal vt100 ~ + // t_k1 [11~ OP *-xterm* + // t_k2 [12~ OQ *-xterm* + // t_k3 [13~ OR *-xterm* + // t_k4 [14~ OS *-xterm* + // t_kh [7~ OH *-xterm* + // t_@7 [4~ OF *-xterm* + case KEYCODE_F1: + return (keyMode == 0) ? "\033OP" : transformForModifiers("\033[1", keyMode, 'P'); + case KEYCODE_F2: + return (keyMode == 0) ? "\033OQ" : transformForModifiers("\033[1", keyMode, 'Q'); + case KEYCODE_F3: + return (keyMode == 0) ? "\033OR" : transformForModifiers("\033[1", keyMode, 'R'); + case KEYCODE_F4: + return (keyMode == 0) ? "\033OS" : transformForModifiers("\033[1", keyMode, 'S'); + case KEYCODE_F5: + return transformForModifiers("\033[15", keyMode, '~'); + case KEYCODE_F6: + return transformForModifiers("\033[17", keyMode, '~'); + case KEYCODE_F7: + return transformForModifiers("\033[18", keyMode, '~'); + case KEYCODE_F8: + return transformForModifiers("\033[19", keyMode, '~'); + case KEYCODE_F9: + return transformForModifiers("\033[20", keyMode, '~'); + case KEYCODE_F10: + return transformForModifiers("\033[21", keyMode, '~'); + case KEYCODE_F11: + return transformForModifiers("\033[23", keyMode, '~'); + case KEYCODE_F12: + return transformForModifiers("\033[24", keyMode, '~'); + + case KEYCODE_SYSRQ: + return "\033[32~"; // Sys Request / Print + // Is this Scroll lock? case Cancel: return "\033[33~"; + case KEYCODE_BREAK: + return "\033[34~"; // Pause/Break + + case KEYCODE_ESCAPE: + case KEYCODE_BACK: + return "\033"; + + case KEYCODE_INSERT: + return transformForModifiers("\033[2", keyMode, '~'); + case KEYCODE_FORWARD_DEL: + return transformForModifiers("\033[3", keyMode, '~'); + + case KEYCODE_PAGE_UP: + return "\033[5~"; + case KEYCODE_PAGE_DOWN: + return "\033[6~"; + case KEYCODE_DEL: + String prefix = ((keyMode & KEYMOD_ALT) == 0) ? "" : "\033"; + // Just do what xterm and gnome-terminal does: + return prefix + (((keyMode & KEYMOD_CTRL) == 0) ? "\u007F" : "\u0008"); + case KEYCODE_NUM_LOCK: + return "\033OP"; + + case KEYCODE_SPACE: + // If ctrl is not down, return null so that it goes through normal input processing (which may e.g. cause a + // combining accent to be written): + return ((keyMode & KEYMOD_CTRL) == 0) ? null : "\0"; + case KEYCODE_TAB: + // This is back-tab_switcher when shifted: + return (keyMode & KEYMOD_SHIFT) == 0 ? "\011" : "\033[Z"; + case KEYCODE_ENTER: + return ((keyMode & KEYMOD_ALT) == 0) ? "\r" : "\033\r"; + + case KEYCODE_NUMPAD_ENTER: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'M') : "\n"; + case KEYCODE_NUMPAD_MULTIPLY: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'j') : "*"; + case KEYCODE_NUMPAD_ADD: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+"; + case KEYCODE_NUMPAD_COMMA: + return ","; + case KEYCODE_NUMPAD_DOT: + return keypadApplication ? "\033On" : "."; + case KEYCODE_NUMPAD_SUBTRACT: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-"; + case KEYCODE_NUMPAD_DIVIDE: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/"; + case KEYCODE_NUMPAD_0: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "0"; + case KEYCODE_NUMPAD_1: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1"; + case KEYCODE_NUMPAD_2: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'r') : "2"; + case KEYCODE_NUMPAD_3: + return keypadApplication ? transformForModifiers("\033O", keyMode, 's') : "3"; + case KEYCODE_NUMPAD_4: + return keypadApplication ? transformForModifiers("\033O", keyMode, 't') : "4"; + case KEYCODE_NUMPAD_5: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'u') : "5"; + case KEYCODE_NUMPAD_6: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'v') : "6"; + case KEYCODE_NUMPAD_7: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'w') : "7"; + case KEYCODE_NUMPAD_8: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'x') : "8"; + case KEYCODE_NUMPAD_9: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'y') : "9"; + case KEYCODE_NUMPAD_EQUALS: + return keypadApplication ? transformForModifiers("\033O", keyMode, 'X') : "="; } - static String getCodeFromTermcap(String termcap, boolean cursorKeysApplication, boolean keypadApplication) { - Integer keyCodeAndMod = TERMCAP_TO_KEYCODE.get(termcap); - if (keyCodeAndMod == null) return null; - int keyCode = keyCodeAndMod; - int keyMod = 0; - if ((keyCode & KEYMOD_SHIFT) != 0) { - keyMod |= KEYMOD_SHIFT; - keyCode &= ~KEYMOD_SHIFT; - } - if ((keyCode & KEYMOD_CTRL) != 0) { - keyMod |= KEYMOD_CTRL; - keyCode &= ~KEYMOD_CTRL; - } - if ((keyCode & KEYMOD_ALT) != 0) { - keyMod |= KEYMOD_ALT; - keyCode &= ~KEYMOD_ALT; - } - return getCode(keyCode, keyMod, cursorKeysApplication, keypadApplication); - } - - public static String getCode(int keyCode, int keyMode, boolean cursorApp, boolean keypadApplication) { - switch (keyCode) { - case KEYCODE_DPAD_CENTER: - return "\015"; - - case KEYCODE_DPAD_UP: - return (keyMode == 0) ? (cursorApp ? "\033OA" : "\033[A") : transformForModifiers("\033[1", keyMode, 'A'); - case KEYCODE_DPAD_DOWN: - return (keyMode == 0) ? (cursorApp ? "\033OB" : "\033[B") : transformForModifiers("\033[1", keyMode, 'B'); - case KEYCODE_DPAD_RIGHT: - return (keyMode == 0) ? (cursorApp ? "\033OC" : "\033[C") : transformForModifiers("\033[1", keyMode, 'C'); - case KEYCODE_DPAD_LEFT: - return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D'); - - case KEYCODE_MOVE_HOME: - // Note that KEYCODE_HOME is handled by the system and never delivered to applications. - // On a Logitech k810 keyboard KEYCODE_MOVE_HOME is sent by FN+LeftArrow. - return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H'); - case KEYCODE_MOVE_END: - return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F'); - - // An xterm can send function keys F1 to F4 in two modes: vt100 compatible or - // not. Because Vim may not know what the xterm is sending, both types of keys - // are recognized. The same happens for the and keys. - // normal vt100 ~ - // t_k1 [11~ OP *-xterm* - // t_k2 [12~ OQ *-xterm* - // t_k3 [13~ OR *-xterm* - // t_k4 [14~ OS *-xterm* - // t_kh [7~ OH *-xterm* - // t_@7 [4~ OF *-xterm* - case KEYCODE_F1: - return (keyMode == 0) ? "\033OP" : transformForModifiers("\033[1", keyMode, 'P'); - case KEYCODE_F2: - return (keyMode == 0) ? "\033OQ" : transformForModifiers("\033[1", keyMode, 'Q'); - case KEYCODE_F3: - return (keyMode == 0) ? "\033OR" : transformForModifiers("\033[1", keyMode, 'R'); - case KEYCODE_F4: - return (keyMode == 0) ? "\033OS" : transformForModifiers("\033[1", keyMode, 'S'); - case KEYCODE_F5: - return transformForModifiers("\033[15", keyMode, '~'); - case KEYCODE_F6: - return transformForModifiers("\033[17", keyMode, '~'); - case KEYCODE_F7: - return transformForModifiers("\033[18", keyMode, '~'); - case KEYCODE_F8: - return transformForModifiers("\033[19", keyMode, '~'); - case KEYCODE_F9: - return transformForModifiers("\033[20", keyMode, '~'); - case KEYCODE_F10: - return transformForModifiers("\033[21", keyMode, '~'); - case KEYCODE_F11: - return transformForModifiers("\033[23", keyMode, '~'); - case KEYCODE_F12: - return transformForModifiers("\033[24", keyMode, '~'); - - case KEYCODE_SYSRQ: - return "\033[32~"; // Sys Request / Print - // Is this Scroll lock? case Cancel: return "\033[33~"; - case KEYCODE_BREAK: - return "\033[34~"; // Pause/Break - - case KEYCODE_ESCAPE: - case KEYCODE_BACK: - return "\033"; - - case KEYCODE_INSERT: - return transformForModifiers("\033[2", keyMode, '~'); - case KEYCODE_FORWARD_DEL: - return transformForModifiers("\033[3", keyMode, '~'); - - case KEYCODE_PAGE_UP: - return "\033[5~"; - case KEYCODE_PAGE_DOWN: - return "\033[6~"; - case KEYCODE_DEL: - String prefix = ((keyMode & KEYMOD_ALT) == 0) ? "" : "\033"; - // Just do what xterm and gnome-terminal does: - return prefix + (((keyMode & KEYMOD_CTRL) == 0) ? "\u007F" : "\u0008"); - case KEYCODE_NUM_LOCK: - return "\033OP"; - - case KEYCODE_SPACE: - // If ctrl is not down, return null so that it goes through normal input processing (which may e.g. cause a - // combining accent to be written): - return ((keyMode & KEYMOD_CTRL) == 0) ? null : "\0"; - case KEYCODE_TAB: - // This is back-tab_switcher when shifted: - return (keyMode & KEYMOD_SHIFT) == 0 ? "\011" : "\033[Z"; - case KEYCODE_ENTER: - return ((keyMode & KEYMOD_ALT) == 0) ? "\r" : "\033\r"; - - case KEYCODE_NUMPAD_ENTER: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'M') : "\n"; - case KEYCODE_NUMPAD_MULTIPLY: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'j') : "*"; - case KEYCODE_NUMPAD_ADD: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+"; - case KEYCODE_NUMPAD_COMMA: - return ","; - case KEYCODE_NUMPAD_DOT: - return keypadApplication ? "\033On" : "."; - case KEYCODE_NUMPAD_SUBTRACT: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-"; - case KEYCODE_NUMPAD_DIVIDE: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/"; - case KEYCODE_NUMPAD_0: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "0"; - case KEYCODE_NUMPAD_1: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1"; - case KEYCODE_NUMPAD_2: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'r') : "2"; - case KEYCODE_NUMPAD_3: - return keypadApplication ? transformForModifiers("\033O", keyMode, 's') : "3"; - case KEYCODE_NUMPAD_4: - return keypadApplication ? transformForModifiers("\033O", keyMode, 't') : "4"; - case KEYCODE_NUMPAD_5: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'u') : "5"; - case KEYCODE_NUMPAD_6: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'v') : "6"; - case KEYCODE_NUMPAD_7: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'w') : "7"; - case KEYCODE_NUMPAD_8: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'x') : "8"; - case KEYCODE_NUMPAD_9: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'y') : "9"; - case KEYCODE_NUMPAD_EQUALS: - return keypadApplication ? transformForModifiers("\033O", keyMode, 'X') : "="; - } - - return null; - } - - private static String transformForModifiers(String start, int keymod, char lastChar) { - int modifier; - switch (keymod) { - case KEYMOD_SHIFT: - modifier = 2; - break; - case KEYMOD_ALT: - modifier = 3; - break; - case (KEYMOD_SHIFT | KEYMOD_ALT): - modifier = 4; - break; - case KEYMOD_CTRL: - modifier = 5; - break; - case KEYMOD_SHIFT | KEYMOD_CTRL: - modifier = 6; - break; - case KEYMOD_ALT | KEYMOD_CTRL: - modifier = 7; - break; - case KEYMOD_SHIFT | KEYMOD_ALT | KEYMOD_CTRL: - modifier = 8; - break; - default: - return start + lastChar; - } - return start + (";" + modifier) + lastChar; + return null; + } + + private static String transformForModifiers(String start, int keymod, char lastChar) { + int modifier; + switch (keymod) { + case KEYMOD_SHIFT: + modifier = 2; + break; + case KEYMOD_ALT: + modifier = 3; + break; + case (KEYMOD_SHIFT | KEYMOD_ALT): + modifier = 4; + break; + case KEYMOD_CTRL: + modifier = 5; + break; + case KEYMOD_SHIFT | KEYMOD_CTRL: + modifier = 6; + break; + case KEYMOD_ALT | KEYMOD_CTRL: + modifier = 7; + break; + case KEYMOD_SHIFT | KEYMOD_ALT | KEYMOD_CTRL: + modifier = 8; + break; + default: + return start + lastChar; } + return start + (";" + modifier) + lastChar; + } } diff --git a/app/src/main/java/io/neoterm/backend/TerminalBuffer.java b/app/src/main/java/io/neoterm/backend/TerminalBuffer.java index e66cbfe..0dd0aab 100755 --- a/app/src/main/java/io/neoterm/backend/TerminalBuffer.java +++ b/app/src/main/java/io/neoterm/backend/TerminalBuffer.java @@ -8,418 +8,428 @@ package io.neoterm.backend; */ public final class TerminalBuffer { - TerminalRow[] mLines; - /** The length of {@link #mLines}. */ - int mTotalRows; - /** The number of rows and columns visible on the screen. */ - int mScreenRows, mColumns; - /** The number of rows kept in history. */ - private int mActiveTranscriptRows = 0; - /** The index in the circular buffer where the visible screen starts. */ - private int mScreenFirstRow = 0; + TerminalRow[] mLines; + /** + * The length of {@link #mLines}. + */ + int mTotalRows; + /** + * The number of rows and columns visible on the screen. + */ + int mScreenRows, mColumns; + /** + * The number of rows kept in history. + */ + private int mActiveTranscriptRows = 0; + /** + * The index in the circular buffer where the visible screen starts. + */ + private int mScreenFirstRow = 0; - /** - * Create a transcript screen. - * - * @param columns the width of the screen in characters. - * @param totalRows the height of the entire text area, in rows of text. - * @param screenRows the height of just the screen, not including the transcript that holds lines that have scrolled off - * the top of the screen. - */ - public TerminalBuffer(int columns, int totalRows, int screenRows) { - mColumns = columns; - mTotalRows = totalRows; - mScreenRows = screenRows; - mLines = new TerminalRow[totalRows]; + /** + * Create a transcript screen. + * + * @param columns the width of the screen in characters. + * @param totalRows the height of the entire text area, in rows of text. + * @param screenRows the height of just the screen, not including the transcript that holds lines that have scrolled off + * the top of the screen. + */ + public TerminalBuffer(int columns, int totalRows, int screenRows) { + mColumns = columns; + mTotalRows = totalRows; + mScreenRows = screenRows; + mLines = new TerminalRow[totalRows]; - blockSet(0, 0, columns, screenRows, ' ', TextStyle.NORMAL); + blockSet(0, 0, columns, screenRows, ' ', TextStyle.NORMAL); + } + + public String getTranscriptText() { + return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim(); + } + + public String getSelectedText(int selX1, int selY1, int selX2, int selY2) { + final StringBuilder builder = new StringBuilder(); + final int columns = mColumns; + + if (selY1 < -getActiveTranscriptRows()) selY1 = -getActiveTranscriptRows(); + if (selY2 >= mScreenRows) selY2 = mScreenRows - 1; + + for (int row = selY1; row <= selY2; row++) { + int x1 = (row == selY1) ? selX1 : 0; + int x2; + if (row == selY2) { + x2 = selX2 + 1; + if (x2 > columns) x2 = columns; + } else { + x2 = columns; + } + TerminalRow lineObject = mLines[externalToInternalRow(row)]; + int x1Index = lineObject.findStartOfColumn(x1); + int x2Index = (x2 < mColumns) ? lineObject.findStartOfColumn(x2) : lineObject.getSpaceUsed(); + if (x2Index == x1Index) { + // Selected the start of a wide character. + x2Index = lineObject.findStartOfColumn(x2 + 1); + } + char[] line = lineObject.mText; + int lastPrintingCharIndex = -1; + int i; + boolean rowLineWrap = getLineWrap(row); + if (rowLineWrap && x2 == columns) { + // If the line was wrapped, we shouldn't lose trailing space: + lastPrintingCharIndex = x2Index - 1; + } else { + for (i = x1Index; i < x2Index; ++i) { + char c = line[i]; + if (c != ' ') lastPrintingCharIndex = i; + } + } + if (lastPrintingCharIndex != -1) + builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1); + if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n'); } + return builder.toString(); + } - public String getTranscriptText() { - return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim(); - } + public int getActiveTranscriptRows() { + return mActiveTranscriptRows; + } - public String getSelectedText(int selX1, int selY1, int selX2, int selY2) { - final StringBuilder builder = new StringBuilder(); - final int columns = mColumns; + public int getActiveRows() { + return mActiveTranscriptRows + mScreenRows; + } - if (selY1 < -getActiveTranscriptRows()) selY1 = -getActiveTranscriptRows(); - if (selY2 >= mScreenRows) selY2 = mScreenRows - 1; + /** + * Convert a row value from the public external coordinate system to our internal private coordinate system. + * + *

+   * - External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
+   * - Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
+   *   mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
+   *
+   * External ↔ Internal:
+   *
+   * [ ...                            ]     [ ...                                     ]
+   * [ -mActiveTranscriptRows         ]     [ mScreenFirstRow - mActiveTranscriptRows ]
+   * [ ...                            ]     [ ...                                     ]
+   * [ 0 (visible screen starts here) ]  ↔  [ mScreenFirstRow                         ]
+   * [ ...                            ]     [ ...                                     ]
+   * [ mScreenRows-1                  ]     [ mScreenFirstRow + mScreenRows-1         ]
+   * 
+ * + * @param externalRow a row in the external coordinate system. + * @return The row corresponding to the input argument in the private coordinate system. + */ + public int externalToInternalRow(int externalRow) { + if (externalRow < -mActiveTranscriptRows || externalRow > mScreenRows) + throw new IllegalArgumentException("extRow=" + externalRow + ", mScreenRows=" + mScreenRows + ", mActiveTranscriptRows=" + mActiveTranscriptRows); + final int internalRow = mScreenFirstRow + externalRow; + return (internalRow < 0) ? (mTotalRows + internalRow) : (internalRow % mTotalRows); + } - for (int row = selY1; row <= selY2; row++) { - int x1 = (row == selY1) ? selX1 : 0; - int x2; - if (row == selY2) { - x2 = selX2 + 1; - if (x2 > columns) x2 = columns; + public void setLineWrap(int row) { + mLines[externalToInternalRow(row)].mLineWrap = true; + } + + public boolean getLineWrap(int row) { + return mLines[externalToInternalRow(row)].mLineWrap; + } + + public void clearLineWrap(int row) { + mLines[externalToInternalRow(row)].mLineWrap = false; + } + + /** + * Resize the screen which this transcript backs. Currently, this only works if the number of columns does not + * change or the rows expand (that is, it only works when shrinking the number of rows). + * + * @param newColumns The number of columns the screen should have. + * @param newRows The number of rows the screen should have. + * @param cursor An int[2] containing the (column, row) cursorColor location. + */ + public void resize(int newColumns, int newRows, int newTotalRows, int[] cursor, long currentStyle, boolean altScreen) { + // newRows > mTotalRows should not normally happen since mTotalRows is TRANSCRIPT_ROWS (10000): + if (newColumns == mColumns && newRows <= mTotalRows) { + // Fast resize where just the rows changed. + int shiftDownOfTopRow = mScreenRows - newRows; + if (shiftDownOfTopRow > 0 && shiftDownOfTopRow < mScreenRows) { + // Shrinking. Check if we can skip blank rows at bottom below cursorColor. + for (int i = mScreenRows - 1; i > 0; i--) { + if (cursor[1] >= i) break; + int r = externalToInternalRow(i); + if (mLines[r] == null || mLines[r].isBlank()) { + if (--shiftDownOfTopRow == 0) break; + } + } + } else if (shiftDownOfTopRow < 0) { + // Negative shift down = expanding. Only move screen up if there is transcript to show: + int actualShift = Math.max(shiftDownOfTopRow, -mActiveTranscriptRows); + if (shiftDownOfTopRow != actualShift) { + // The new lines revealed by the resizing are not all from the transcript. Blank the below ones. + for (int i = 0; i < actualShift - shiftDownOfTopRow; i++) + allocateFullLineIfNecessary((mScreenFirstRow + mScreenRows + i) % mTotalRows).clear(currentStyle); + shiftDownOfTopRow = actualShift; + } + } + mScreenFirstRow += shiftDownOfTopRow; + mScreenFirstRow = (mScreenFirstRow < 0) ? (mScreenFirstRow + mTotalRows) : (mScreenFirstRow % mTotalRows); + mTotalRows = newTotalRows; + mActiveTranscriptRows = altScreen ? 0 : Math.max(0, mActiveTranscriptRows + shiftDownOfTopRow); + cursor[1] -= shiftDownOfTopRow; + mScreenRows = newRows; + } else { + // Copy away old state and update new: + TerminalRow[] oldLines = mLines; + mLines = new TerminalRow[newTotalRows]; + for (int i = 0; i < newTotalRows; i++) + mLines[i] = new TerminalRow(newColumns, currentStyle); + + final int oldActiveTranscriptRows = mActiveTranscriptRows; + final int oldScreenFirstRow = mScreenFirstRow; + final int oldScreenRows = mScreenRows; + final int oldTotalRows = mTotalRows; + mTotalRows = newTotalRows; + mScreenRows = newRows; + mActiveTranscriptRows = mScreenFirstRow = 0; + mColumns = newColumns; + + int newCursorRow = -1; + int newCursorColumn = -1; + int oldCursorRow = cursor[1]; + int oldCursorColumn = cursor[0]; + boolean newCursorPlaced = false; + + int currentOutputExternalRow = 0; + int currentOutputExternalColumn = 0; + + // Loop over every character in the initial state. + // Blank lines should be skipped only if at end of transcript (just as is done in the "fast" resize), so we + // keep track how many blank lines we have skipped if we later on find a non-blank line. + int skippedBlankLines = 0; + for (int externalOldRow = -oldActiveTranscriptRows; externalOldRow < oldScreenRows; externalOldRow++) { + // Do what externalToInternalRow() does but for the old state: + int internalOldRow = oldScreenFirstRow + externalOldRow; + internalOldRow = (internalOldRow < 0) ? (oldTotalRows + internalOldRow) : (internalOldRow % oldTotalRows); + + TerminalRow oldLine = oldLines[internalOldRow]; + boolean cursorAtThisRow = externalOldRow == oldCursorRow; + // The cursorColor may only be on a non-null line, which we should not skip: + if (oldLine == null || (!(!newCursorPlaced && cursorAtThisRow)) && oldLine.isBlank()) { + skippedBlankLines++; + continue; + } else if (skippedBlankLines > 0) { + // After skipping some blank lines we encounter a non-blank line. Insert the skipped blank lines. + for (int i = 0; i < skippedBlankLines; i++) { + if (currentOutputExternalRow == mScreenRows - 1) { + scrollDownOneLine(0, mScreenRows, currentStyle); } else { - x2 = columns; + currentOutputExternalRow++; } - TerminalRow lineObject = mLines[externalToInternalRow(row)]; - int x1Index = lineObject.findStartOfColumn(x1); - int x2Index = (x2 < mColumns) ? lineObject.findStartOfColumn(x2) : lineObject.getSpaceUsed(); - if (x2Index == x1Index) { - // Selected the start of a wide character. - x2Index = lineObject.findStartOfColumn(x2 + 1); - } - char[] line = lineObject.mText; - int lastPrintingCharIndex = -1; - int i; - boolean rowLineWrap = getLineWrap(row); - if (rowLineWrap && x2 == columns) { - // If the line was wrapped, we shouldn't lose trailing space: - lastPrintingCharIndex = x2Index - 1; + currentOutputExternalColumn = 0; + } + skippedBlankLines = 0; + } + + int lastNonSpaceIndex = 0; + boolean justToCursor = false; + if (cursorAtThisRow || oldLine.mLineWrap) { + // Take the whole line, either because of cursorColor on it, or if line wrapping. + lastNonSpaceIndex = oldLine.getSpaceUsed(); + if (cursorAtThisRow) justToCursor = true; + } else { + for (int i = 0; i < oldLine.getSpaceUsed(); i++) + // NEWLY INTRODUCED BUG! Should not index oldLine.mStyle with char indices + if (oldLine.mText[i] != ' '/* || oldLine.mStyle[i] != currentStyle */) + lastNonSpaceIndex = i + 1; + } + + int currentOldCol = 0; + long styleAtCol = 0; + for (int i = 0; i < lastNonSpaceIndex; i++) { + // Note that looping over java character, not cells. + char c = oldLine.mText[i]; + int codePoint = (Character.isHighSurrogate(c)) ? Character.toCodePoint(c, oldLine.mText[++i]) : c; + int displayWidth = WcWidth.width(codePoint); + // Use the last style if this is a zero-width character: + if (displayWidth > 0) styleAtCol = oldLine.getStyle(currentOldCol); + + // Line wrap as necessary: + if (currentOutputExternalColumn + displayWidth > mColumns) { + setLineWrap(currentOutputExternalRow); + if (currentOutputExternalRow == mScreenRows - 1) { + if (newCursorPlaced) newCursorRow--; + scrollDownOneLine(0, mScreenRows, currentStyle); } else { - for (i = x1Index; i < x2Index; ++i) { - char c = line[i]; - if (c != ' ') lastPrintingCharIndex = i; - } + currentOutputExternalRow++; } - if (lastPrintingCharIndex != -1) - builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1); - if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n'); + currentOutputExternalColumn = 0; + } + + int offsetDueToCombiningChar = ((displayWidth <= 0 && currentOutputExternalColumn > 0) ? 1 : 0); + int outputColumn = currentOutputExternalColumn - offsetDueToCombiningChar; + setChar(outputColumn, currentOutputExternalRow, codePoint, styleAtCol); + + if (displayWidth > 0) { + if (oldCursorRow == externalOldRow && oldCursorColumn == currentOldCol) { + newCursorColumn = currentOutputExternalColumn; + newCursorRow = currentOutputExternalRow; + newCursorPlaced = true; + } + currentOldCol += displayWidth; + currentOutputExternalColumn += displayWidth; + if (justToCursor && newCursorPlaced) break; + } } - return builder.toString(); + // Old row has been copied. Check if we need to insert newline if old line was not wrapping: + if (externalOldRow != (oldScreenRows - 1) && !oldLine.mLineWrap) { + if (currentOutputExternalRow == mScreenRows - 1) { + if (newCursorPlaced) newCursorRow--; + scrollDownOneLine(0, mScreenRows, currentStyle); + } else { + currentOutputExternalRow++; + } + currentOutputExternalColumn = 0; + } + } + + cursor[0] = newCursorColumn; + cursor[1] = newCursorRow; } - public int getActiveTranscriptRows() { - return mActiveTranscriptRows; - } + // Handle cursorColor scrolling off screen: + if (cursor[0] < 0 || cursor[1] < 0) cursor[0] = cursor[1] = 0; + } - public int getActiveRows() { - return mActiveTranscriptRows + mScreenRows; - } + /** + * Block copy lines and associated metadata from one location to another in the circular buffer, taking wraparound + * into account. + * + * @param srcInternal The first line to be copied. + * @param len The number of lines to be copied. + */ + private void blockCopyLinesDown(int srcInternal, int len) { + if (len == 0) return; + int totalRows = mTotalRows; - /** - * Convert a row value from the public external coordinate system to our internal private coordinate system. - * - *
-     * - External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
-     * - Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
-     *   mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
-     *
-     * External ↔ Internal:
-     *
-     * [ ...                            ]     [ ...                                     ]
-     * [ -mActiveTranscriptRows         ]     [ mScreenFirstRow - mActiveTranscriptRows ]
-     * [ ...                            ]     [ ...                                     ]
-     * [ 0 (visible screen starts here) ]  ↔  [ mScreenFirstRow                         ]
-     * [ ...                            ]     [ ...                                     ]
-     * [ mScreenRows-1                  ]     [ mScreenFirstRow + mScreenRows-1         ]
-     * 
- * - * @param externalRow a row in the external coordinate system. - * @return The row corresponding to the input argument in the private coordinate system. - */ - public int externalToInternalRow(int externalRow) { - if (externalRow < -mActiveTranscriptRows || externalRow > mScreenRows) - throw new IllegalArgumentException("extRow=" + externalRow + ", mScreenRows=" + mScreenRows + ", mActiveTranscriptRows=" + mActiveTranscriptRows); - final int internalRow = mScreenFirstRow + externalRow; - return (internalRow < 0) ? (mTotalRows + internalRow) : (internalRow % mTotalRows); - } + int start = len - 1; + // Save away line to be overwritten: + TerminalRow lineToBeOverWritten = mLines[(srcInternal + start + 1) % totalRows]; + // Do the copy from bottom to top. + for (int i = start; i >= 0; --i) + mLines[(srcInternal + i + 1) % totalRows] = mLines[(srcInternal + i) % totalRows]; + // Put back overwritten line, now above the block: + mLines[(srcInternal) % totalRows] = lineToBeOverWritten; + } - public void setLineWrap(int row) { - mLines[externalToInternalRow(row)].mLineWrap = true; - } + /** + * Scroll the screen down one line. To scroll the whole screen of a 24 line screen, the arguments would be (0, 24). + * + * @param topMargin First line that is scrolled. + * @param bottomMargin One line after the last line that is scrolled. + * @param style the style for the newly exposed line. + */ + public void scrollDownOneLine(int topMargin, int bottomMargin, long style) { + if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows) + throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + mScreenRows); - public boolean getLineWrap(int row) { - return mLines[externalToInternalRow(row)].mLineWrap; - } + // Copy the fixed topMargin lines one line down so that they remain on screen in same position: + blockCopyLinesDown(mScreenFirstRow, topMargin); + // Copy the fixed mScreenRows-bottomMargin lines one line down so that they remain on screen in same + // position: + blockCopyLinesDown(externalToInternalRow(bottomMargin), mScreenRows - bottomMargin); - public void clearLineWrap(int row) { - mLines[externalToInternalRow(row)].mLineWrap = false; - } + // Update the screen location in the ring buffer: + mScreenFirstRow = (mScreenFirstRow + 1) % mTotalRows; + // Note that the history has grown if not already full: + if (mActiveTranscriptRows < mTotalRows - mScreenRows) mActiveTranscriptRows++; - /** - * Resize the screen which this transcript backs. Currently, this only works if the number of columns does not - * change or the rows expand (that is, it only works when shrinking the number of rows). - * - * @param newColumns The number of columns the screen should have. - * @param newRows The number of rows the screen should have. - * @param cursor An int[2] containing the (column, row) cursorColor location. - */ - public void resize(int newColumns, int newRows, int newTotalRows, int[] cursor, long currentStyle, boolean altScreen) { - // newRows > mTotalRows should not normally happen since mTotalRows is TRANSCRIPT_ROWS (10000): - if (newColumns == mColumns && newRows <= mTotalRows) { - // Fast resize where just the rows changed. - int shiftDownOfTopRow = mScreenRows - newRows; - if (shiftDownOfTopRow > 0 && shiftDownOfTopRow < mScreenRows) { - // Shrinking. Check if we can skip blank rows at bottom below cursorColor. - for (int i = mScreenRows - 1; i > 0; i--) { - if (cursor[1] >= i) break; - int r = externalToInternalRow(i); - if (mLines[r] == null || mLines[r].isBlank()) { - if (--shiftDownOfTopRow == 0) break; - } - } - } else if (shiftDownOfTopRow < 0) { - // Negative shift down = expanding. Only move screen up if there is transcript to show: - int actualShift = Math.max(shiftDownOfTopRow, -mActiveTranscriptRows); - if (shiftDownOfTopRow != actualShift) { - // The new lines revealed by the resizing are not all from the transcript. Blank the below ones. - for (int i = 0; i < actualShift - shiftDownOfTopRow; i++) - allocateFullLineIfNecessary((mScreenFirstRow + mScreenRows + i) % mTotalRows).clear(currentStyle); - shiftDownOfTopRow = actualShift; - } - } - mScreenFirstRow += shiftDownOfTopRow; - mScreenFirstRow = (mScreenFirstRow < 0) ? (mScreenFirstRow + mTotalRows) : (mScreenFirstRow % mTotalRows); - mTotalRows = newTotalRows; - mActiveTranscriptRows = altScreen ? 0 : Math.max(0, mActiveTranscriptRows + shiftDownOfTopRow); - cursor[1] -= shiftDownOfTopRow; - mScreenRows = newRows; + // Blank the newly revealed line above the bottom margin: + int blankRow = externalToInternalRow(bottomMargin - 1); + if (mLines[blankRow] == null) { + mLines[blankRow] = new TerminalRow(mColumns, style); + } else { + mLines[blankRow].clear(style); + } + } + + /** + * Block copy characters from one position in the screen to another. The two positions can overlap. All characters + * of the source and destination must be within the bounds of the screen, or else an InvalidParameterException will + * be thrown. + * + * @param sx source X coordinate + * @param sy source Y coordinate + * @param w width + * @param h height + * @param dx destination X coordinate + * @param dy destination Y coordinate + */ + public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { + if (w == 0) return; + if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows || dx < 0 || dx + w > mColumns || dy < 0 || dy + h > mScreenRows) + throw new IllegalArgumentException(); + boolean copyingUp = sy > dy; + for (int y = 0; y < h; y++) { + int y2 = copyingUp ? y : (h - (y + 1)); + TerminalRow sourceRow = allocateFullLineIfNecessary(externalToInternalRow(sy + y2)); + allocateFullLineIfNecessary(externalToInternalRow(dy + y2)).copyInterval(sourceRow, sx, sx + w, dx); + } + } + + /** + * Block set characters. All characters must be within the bounds of the screen, or else and + * InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block + * of characters. + */ + public void blockSet(int sx, int sy, int w, int h, int val, long style) { + if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) { + throw new IllegalArgumentException( + "Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")"); + } + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + setChar(sx + x, sy + y, val, style); + } + + public TerminalRow allocateFullLineIfNecessary(int row) { + return (mLines[row] == null) ? (mLines[row] = new TerminalRow(mColumns, 0)) : mLines[row]; + } + + public void setChar(int column, int row, int codePoint, long style) { + if (row >= mScreenRows || column >= mColumns) + throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns); + row = externalToInternalRow(row); + allocateFullLineIfNecessary(row).setChar(column, codePoint, style); + } + + public long getStyleAt(int externalRow, int column) { + return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column); + } + + /** + * Support for http://vt100.net/docs/vt510-rm/DECCARA and http://vt100.net/docs/vt510-rm/DECCARA + */ + public void setOrClearEffect(int bits, boolean setOrClear, boolean reverse, boolean rectangular, int leftMargin, int rightMargin, int top, int left, + int bottom, int right) { + for (int y = top; y < bottom; y++) { + TerminalRow line = mLines[externalToInternalRow(y)]; + int startOfLine = (rectangular || y == top) ? left : leftMargin; + int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin; + for (int x = startOfLine; x < endOfLine; x++) { + long currentStyle = line.getStyle(x); + int foreColor = TextStyle.decodeForeColor(currentStyle); + int backColor = TextStyle.decodeBackColor(currentStyle); + int effect = TextStyle.decodeEffect(currentStyle); + if (reverse) { + // Clear out the bits to reverse and add them back in reversed: + effect = (effect & ~bits) | (bits & ~effect); + } else if (setOrClear) { + effect |= bits; } else { - // Copy away old state and update new: - TerminalRow[] oldLines = mLines; - mLines = new TerminalRow[newTotalRows]; - for (int i = 0; i < newTotalRows; i++) - mLines[i] = new TerminalRow(newColumns, currentStyle); - - final int oldActiveTranscriptRows = mActiveTranscriptRows; - final int oldScreenFirstRow = mScreenFirstRow; - final int oldScreenRows = mScreenRows; - final int oldTotalRows = mTotalRows; - mTotalRows = newTotalRows; - mScreenRows = newRows; - mActiveTranscriptRows = mScreenFirstRow = 0; - mColumns = newColumns; - - int newCursorRow = -1; - int newCursorColumn = -1; - int oldCursorRow = cursor[1]; - int oldCursorColumn = cursor[0]; - boolean newCursorPlaced = false; - - int currentOutputExternalRow = 0; - int currentOutputExternalColumn = 0; - - // Loop over every character in the initial state. - // Blank lines should be skipped only if at end of transcript (just as is done in the "fast" resize), so we - // keep track how many blank lines we have skipped if we later on find a non-blank line. - int skippedBlankLines = 0; - for (int externalOldRow = -oldActiveTranscriptRows; externalOldRow < oldScreenRows; externalOldRow++) { - // Do what externalToInternalRow() does but for the old state: - int internalOldRow = oldScreenFirstRow + externalOldRow; - internalOldRow = (internalOldRow < 0) ? (oldTotalRows + internalOldRow) : (internalOldRow % oldTotalRows); - - TerminalRow oldLine = oldLines[internalOldRow]; - boolean cursorAtThisRow = externalOldRow == oldCursorRow; - // The cursorColor may only be on a non-null line, which we should not skip: - if (oldLine == null || (!(!newCursorPlaced && cursorAtThisRow)) && oldLine.isBlank()) { - skippedBlankLines++; - continue; - } else if (skippedBlankLines > 0) { - // After skipping some blank lines we encounter a non-blank line. Insert the skipped blank lines. - for (int i = 0; i < skippedBlankLines; i++) { - if (currentOutputExternalRow == mScreenRows - 1) { - scrollDownOneLine(0, mScreenRows, currentStyle); - } else { - currentOutputExternalRow++; - } - currentOutputExternalColumn = 0; - } - skippedBlankLines = 0; - } - - int lastNonSpaceIndex = 0; - boolean justToCursor = false; - if (cursorAtThisRow || oldLine.mLineWrap) { - // Take the whole line, either because of cursorColor on it, or if line wrapping. - lastNonSpaceIndex = oldLine.getSpaceUsed(); - if (cursorAtThisRow) justToCursor = true; - } else { - for (int i = 0; i < oldLine.getSpaceUsed(); i++) - // NEWLY INTRODUCED BUG! Should not index oldLine.mStyle with char indices - if (oldLine.mText[i] != ' '/* || oldLine.mStyle[i] != currentStyle */) - lastNonSpaceIndex = i + 1; - } - - int currentOldCol = 0; - long styleAtCol = 0; - for (int i = 0; i < lastNonSpaceIndex; i++) { - // Note that looping over java character, not cells. - char c = oldLine.mText[i]; - int codePoint = (Character.isHighSurrogate(c)) ? Character.toCodePoint(c, oldLine.mText[++i]) : c; - int displayWidth = WcWidth.width(codePoint); - // Use the last style if this is a zero-width character: - if (displayWidth > 0) styleAtCol = oldLine.getStyle(currentOldCol); - - // Line wrap as necessary: - if (currentOutputExternalColumn + displayWidth > mColumns) { - setLineWrap(currentOutputExternalRow); - if (currentOutputExternalRow == mScreenRows - 1) { - if (newCursorPlaced) newCursorRow--; - scrollDownOneLine(0, mScreenRows, currentStyle); - } else { - currentOutputExternalRow++; - } - currentOutputExternalColumn = 0; - } - - int offsetDueToCombiningChar = ((displayWidth <= 0 && currentOutputExternalColumn > 0) ? 1 : 0); - int outputColumn = currentOutputExternalColumn - offsetDueToCombiningChar; - setChar(outputColumn, currentOutputExternalRow, codePoint, styleAtCol); - - if (displayWidth > 0) { - if (oldCursorRow == externalOldRow && oldCursorColumn == currentOldCol) { - newCursorColumn = currentOutputExternalColumn; - newCursorRow = currentOutputExternalRow; - newCursorPlaced = true; - } - currentOldCol += displayWidth; - currentOutputExternalColumn += displayWidth; - if (justToCursor && newCursorPlaced) break; - } - } - // Old row has been copied. Check if we need to insert newline if old line was not wrapping: - if (externalOldRow != (oldScreenRows - 1) && !oldLine.mLineWrap) { - if (currentOutputExternalRow == mScreenRows - 1) { - if (newCursorPlaced) newCursorRow--; - scrollDownOneLine(0, mScreenRows, currentStyle); - } else { - currentOutputExternalRow++; - } - currentOutputExternalColumn = 0; - } - } - - cursor[0] = newCursorColumn; - cursor[1] = newCursorRow; - } - - // Handle cursorColor scrolling off screen: - if (cursor[0] < 0 || cursor[1] < 0) cursor[0] = cursor[1] = 0; - } - - /** - * Block copy lines and associated metadata from one location to another in the circular buffer, taking wraparound - * into account. - * - * @param srcInternal The first line to be copied. - * @param len The number of lines to be copied. - */ - private void blockCopyLinesDown(int srcInternal, int len) { - if (len == 0) return; - int totalRows = mTotalRows; - - int start = len - 1; - // Save away line to be overwritten: - TerminalRow lineToBeOverWritten = mLines[(srcInternal + start + 1) % totalRows]; - // Do the copy from bottom to top. - for (int i = start; i >= 0; --i) - mLines[(srcInternal + i + 1) % totalRows] = mLines[(srcInternal + i) % totalRows]; - // Put back overwritten line, now above the block: - mLines[(srcInternal) % totalRows] = lineToBeOverWritten; - } - - /** - * Scroll the screen down one line. To scroll the whole screen of a 24 line screen, the arguments would be (0, 24). - * - * @param topMargin First line that is scrolled. - * @param bottomMargin One line after the last line that is scrolled. - * @param style the style for the newly exposed line. - */ - public void scrollDownOneLine(int topMargin, int bottomMargin, long style) { - if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows) - throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + mScreenRows); - - // Copy the fixed topMargin lines one line down so that they remain on screen in same position: - blockCopyLinesDown(mScreenFirstRow, topMargin); - // Copy the fixed mScreenRows-bottomMargin lines one line down so that they remain on screen in same - // position: - blockCopyLinesDown(externalToInternalRow(bottomMargin), mScreenRows - bottomMargin); - - // Update the screen location in the ring buffer: - mScreenFirstRow = (mScreenFirstRow + 1) % mTotalRows; - // Note that the history has grown if not already full: - if (mActiveTranscriptRows < mTotalRows - mScreenRows) mActiveTranscriptRows++; - - // Blank the newly revealed line above the bottom margin: - int blankRow = externalToInternalRow(bottomMargin - 1); - if (mLines[blankRow] == null) { - mLines[blankRow] = new TerminalRow(mColumns, style); - } else { - mLines[blankRow].clear(style); - } - } - - /** - * Block copy characters from one position in the screen to another. The two positions can overlap. All characters - * of the source and destination must be within the bounds of the screen, or else an InvalidParameterException will - * be thrown. - * - * @param sx source X coordinate - * @param sy source Y coordinate - * @param w width - * @param h height - * @param dx destination X coordinate - * @param dy destination Y coordinate - */ - public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { - if (w == 0) return; - if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows || dx < 0 || dx + w > mColumns || dy < 0 || dy + h > mScreenRows) - throw new IllegalArgumentException(); - boolean copyingUp = sy > dy; - for (int y = 0; y < h; y++) { - int y2 = copyingUp ? y : (h - (y + 1)); - TerminalRow sourceRow = allocateFullLineIfNecessary(externalToInternalRow(sy + y2)); - allocateFullLineIfNecessary(externalToInternalRow(dy + y2)).copyInterval(sourceRow, sx, sx + w, dx); - } - } - - /** - * Block set characters. All characters must be within the bounds of the screen, or else and - * InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block - * of characters. - */ - public void blockSet(int sx, int sy, int w, int h, int val, long style) { - if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) { - throw new IllegalArgumentException( - "Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")"); - } - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) - setChar(sx + x, sy + y, val, style); - } - - public TerminalRow allocateFullLineIfNecessary(int row) { - return (mLines[row] == null) ? (mLines[row] = new TerminalRow(mColumns, 0)) : mLines[row]; - } - - public void setChar(int column, int row, int codePoint, long style) { - if (row >= mScreenRows || column >= mColumns) - throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns); - row = externalToInternalRow(row); - allocateFullLineIfNecessary(row).setChar(column, codePoint, style); - } - - public long getStyleAt(int externalRow, int column) { - return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column); - } - - /** Support for http://vt100.net/docs/vt510-rm/DECCARA and http://vt100.net/docs/vt510-rm/DECCARA */ - public void setOrClearEffect(int bits, boolean setOrClear, boolean reverse, boolean rectangular, int leftMargin, int rightMargin, int top, int left, - int bottom, int right) { - for (int y = top; y < bottom; y++) { - TerminalRow line = mLines[externalToInternalRow(y)]; - int startOfLine = (rectangular || y == top) ? left : leftMargin; - int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin; - for (int x = startOfLine; x < endOfLine; x++) { - long currentStyle = line.getStyle(x); - int foreColor = TextStyle.decodeForeColor(currentStyle); - int backColor = TextStyle.decodeBackColor(currentStyle); - int effect = TextStyle.decodeEffect(currentStyle); - if (reverse) { - // Clear out the bits to reverse and add them back in reversed: - effect = (effect & ~bits) | (bits & ~effect); - } else if (setOrClear) { - effect |= bits; - } else { - effect &= ~bits; - } - line.mStyle[x] = TextStyle.encode(foreColor, backColor, effect); - } + effect &= ~bits; } + line.mStyle[x] = TextStyle.encode(foreColor, backColor, effect); + } } + } } diff --git a/app/src/main/java/io/neoterm/backend/TerminalColorScheme.java b/app/src/main/java/io/neoterm/backend/TerminalColorScheme.java index e5eed18..931c75b 100755 --- a/app/src/main/java/io/neoterm/backend/TerminalColorScheme.java +++ b/app/src/main/java/io/neoterm/backend/TerminalColorScheme.java @@ -11,110 +11,112 @@ import java.util.Properties; */ public final class TerminalColorScheme { - /** http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg, but with blue color brighter. */ - public static final int[] DEFAULT_COLORS = { - // 16 original colors. First 8 are dim. - 0xff000000, // black - 0xffcd0000, // dim red - 0xff00cd00, // dim green - 0xffcdcd00, // dim yellow - 0xff6495ed, // dim blue - 0xffcd00cd, // dim magenta - 0xff00cdcd, // dim cyan - 0xffe5e5e5, // dim white - // Second 8 are bright: - 0xff7f7f7f, // medium grey - 0xffff0000, // bright red - 0xff00ff00, // bright green - 0xffffff00, // bright yellow - 0xff5c5cff, // light blue - 0xffff00ff, // bright magenta - 0xff00ffff, // bright cyan - 0xffffffff, // bright white + /** + * http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg, but with blue color brighter. + */ + public static final int[] DEFAULT_COLORS = { + // 16 original colors. First 8 are dim. + 0xff000000, // black + 0xffcd0000, // dim red + 0xff00cd00, // dim green + 0xffcdcd00, // dim yellow + 0xff6495ed, // dim blue + 0xffcd00cd, // dim magenta + 0xff00cdcd, // dim cyan + 0xffe5e5e5, // dim white + // Second 8 are bright: + 0xff7f7f7f, // medium grey + 0xffff0000, // bright red + 0xff00ff00, // bright green + 0xffffff00, // bright yellow + 0xff5c5cff, // light blue + 0xffff00ff, // bright magenta + 0xff00ffff, // bright cyan + 0xffffffff, // bright white - // 216 color cube, six shades of each color: - 0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, 0xff005fd7, 0xff005fff, - 0xff008700, 0xff00875f, 0xff008787, 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff, - 0xff00d700, 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, 0xff00ffff, - 0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff, - 0xff5f8700, 0xff5f875f, 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff, - 0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, 0xff5fffd7, 0xff5fffff, - 0xff870000, 0xff87005f, 0xff870087, 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff, - 0xff878700, 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, 0xff87afff, - 0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, 0xff87ffaf, 0xff87ffd7, 0xff87ffff, - 0xffaf0000, 0xffaf005f, 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff, - 0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, 0xffafafd7, 0xffafafff, - 0xffafd700, 0xffafd75f, 0xffafd787, 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff, - 0xffd70000, 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, 0xffd75fff, - 0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, 0xffd7afaf, 0xffd7afd7, 0xffd7afff, - 0xffd7d700, 0xffd7d75f, 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff, - 0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, 0xffff5fd7, 0xffff5fff, - 0xffff8700, 0xffff875f, 0xffff8787, 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff, - 0xffffd700, 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, 0xffffffff, + // 216 color cube, six shades of each color: + 0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, 0xff005fd7, 0xff005fff, + 0xff008700, 0xff00875f, 0xff008787, 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff, + 0xff00d700, 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, 0xff00ffff, + 0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff, + 0xff5f8700, 0xff5f875f, 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff, + 0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, 0xff5fffd7, 0xff5fffff, + 0xff870000, 0xff87005f, 0xff870087, 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff, + 0xff878700, 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, 0xff87afff, + 0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, 0xff87ffaf, 0xff87ffd7, 0xff87ffff, + 0xffaf0000, 0xffaf005f, 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff, + 0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, 0xffafafd7, 0xffafafff, + 0xffafd700, 0xffafd75f, 0xffafd787, 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff, + 0xffd70000, 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, 0xffd75fff, + 0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, 0xffd7afaf, 0xffd7afd7, 0xffd7afff, + 0xffd7d700, 0xffd7d75f, 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff, + 0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, 0xffff5fd7, 0xffff5fff, + 0xffff8700, 0xffff875f, 0xffff8787, 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff, + 0xffffd700, 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, 0xffffffff, - // 24 grey scale ramp: - 0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff626262, 0xff6c6c6c, 0xff767676, - 0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee, + // 24 grey scale ramp: + 0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff626262, 0xff6c6c6c, 0xff767676, + 0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee, - // COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR: - 0xffffffff, 0xff000000, 0xffA9AAA9}; + // COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR: + 0xffffffff, 0xff000000, 0xffA9AAA9}; - public final int[] mDefaultColors = new int[TextStyle.NUM_INDEXED_COLORS]; + public final int[] mDefaultColors = new int[TextStyle.NUM_INDEXED_COLORS]; - public TerminalColorScheme() { - reset(); + public TerminalColorScheme() { + reset(); + } + + private void reset() { + System.arraycopy(DEFAULT_COLORS, 0, mDefaultColors, 0, TextStyle.NUM_INDEXED_COLORS); + } + + public void updateWith(String foreground, String background, String cursor, Map color) { + Properties prop = new Properties(); + if (foreground != null) { + prop.put("foreground", foreground); } - - private void reset() { - System.arraycopy(DEFAULT_COLORS, 0, mDefaultColors, 0, TextStyle.NUM_INDEXED_COLORS); + if (background != null) { + prop.put("background", background); } - - public void updateWith(String foreground, String background, String cursor, Map color) { - Properties prop = new Properties(); - if (foreground != null) { - prop.put("foreground", foreground); - } - if (background != null) { - prop.put("background", background); - } - if (cursor != null) { - prop.put("cursor", cursor); - } - for (int i : color.keySet()) { - prop.put("color" + i, color.get(i)); - } - updateWith(prop); + if (cursor != null) { + prop.put("cursor", cursor); } - - public void updateWith(Properties props) { - reset(); - for (Map.Entry entries : props.entrySet()) { - String key = (String) entries.getKey(); - String value = (String) entries.getValue(); - int colorIndex; - - if (key.equals("foreground")) { - colorIndex = TextStyle.COLOR_INDEX_FOREGROUND; - } else if (key.equals("background")) { - colorIndex = TextStyle.COLOR_INDEX_BACKGROUND; - } else if (key.equals("cursor")) { - colorIndex = TextStyle.COLOR_INDEX_CURSOR; - } else if (key.startsWith("color")) { - try { - colorIndex = Integer.parseInt(key.substring(5)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid property: '" + key + "'"); - } - } else { - throw new IllegalArgumentException("Invalid property: '" + key + "'"); - } - - int colorValue = TerminalColors.parse(value); - if (colorValue == 0) - throw new IllegalArgumentException("Property '" + key + "' has invalid color: '" + value + "'"); - - mDefaultColors[colorIndex] = colorValue; - } + for (int i : color.keySet()) { + prop.put("color" + i, color.get(i)); } + updateWith(prop); + } + + public void updateWith(Properties props) { + reset(); + for (Map.Entry entries : props.entrySet()) { + String key = (String) entries.getKey(); + String value = (String) entries.getValue(); + int colorIndex; + + if (key.equals("foreground")) { + colorIndex = TextStyle.COLOR_INDEX_FOREGROUND; + } else if (key.equals("background")) { + colorIndex = TextStyle.COLOR_INDEX_BACKGROUND; + } else if (key.equals("cursor")) { + colorIndex = TextStyle.COLOR_INDEX_CURSOR; + } else if (key.startsWith("color")) { + try { + colorIndex = Integer.parseInt(key.substring(5)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid property: '" + key + "'"); + } + } else { + throw new IllegalArgumentException("Invalid property: '" + key + "'"); + } + + int colorValue = TerminalColors.parse(value); + if (colorValue == 0) + throw new IllegalArgumentException("Property '" + key + "' has invalid color: '" + value + "'"); + + mDefaultColors[colorIndex] = colorValue; + } + } } diff --git a/app/src/main/java/io/neoterm/backend/TerminalColors.java b/app/src/main/java/io/neoterm/backend/TerminalColors.java index 9e4399e..c45e9e0 100755 --- a/app/src/main/java/io/neoterm/backend/TerminalColors.java +++ b/app/src/main/java/io/neoterm/backend/TerminalColors.java @@ -1,81 +1,93 @@ package io.neoterm.backend; -/** Current terminal colors (if different from default). */ +/** + * Current terminal colors (if different from default). + */ public final class TerminalColors { - /** Static data - a bit ugly but ok for now. */ - public static final TerminalColorScheme COLOR_SCHEME = new TerminalColorScheme(); + /** + * Static data - a bit ugly but ok for now. + */ + public static final TerminalColorScheme COLOR_SCHEME = new TerminalColorScheme(); - /** - * The current terminal colors, which are normally set from the color theme, but may be set dynamically with the OSC - * 4 control sequence. - */ - public final int[] mCurrentColors = new int[TextStyle.NUM_INDEXED_COLORS]; + /** + * The current terminal colors, which are normally set from the color theme, but may be set dynamically with the OSC + * 4 control sequence. + */ + public final int[] mCurrentColors = new int[TextStyle.NUM_INDEXED_COLORS]; - /** Create a new instance with default colors from the theme. */ - public TerminalColors() { - reset(); + /** + * Create a new instance with default colors from the theme. + */ + public TerminalColors() { + reset(); + } + + /** + * Reset a particular indexed color with the default color from the color theme. + */ + public void reset(int index) { + mCurrentColors[index] = COLOR_SCHEME.mDefaultColors[index]; + } + + /** + * Reset all indexed colors with the default color from the color theme. + */ + public void reset() { + reset(COLOR_SCHEME); + } + + public void reset(TerminalColorScheme colorScheme) { + System.arraycopy(colorScheme.mDefaultColors, 0, mCurrentColors, 0, TextStyle.NUM_INDEXED_COLORS); + } + + /** + * Parse color according to http://manpages.ubuntu.com/manpages/intrepid/man3/XQueryColor.3.html + *

+ * Highest bit is set if successful, so return value is 0xFF${R}${G}${B}. Return 0 if failed. + */ + public static int parse(String c) { + try { + int skipInitial, skipBetween; + if (c.charAt(0) == '#') { + // #RGB, #RRGGBB, #RRRGGGBBB or #RRRRGGGGBBBB. Most significant bits. + skipInitial = 1; + skipBetween = 0; + } else if (c.startsWith("rgb:")) { + // rgb:// where , , := h | hh | hhh | hhhh. Scaled. + skipInitial = 4; + skipBetween = 1; + } else { + // assume that c is an int + return Integer.parseInt(c); + } + int charsForColors = c.length() - skipInitial - 2 * skipBetween; + if (charsForColors % 3 != 0) return 0; // Unequal lengths. + int componentLength = charsForColors / 3; + double mult = 255 / (Math.pow(2, componentLength * 4) - 1); + + int currentPosition = skipInitial; + String rString = c.substring(currentPosition, currentPosition + componentLength); + currentPosition += componentLength + skipBetween; + String gString = c.substring(currentPosition, currentPosition + componentLength); + currentPosition += componentLength + skipBetween; + String bString = c.substring(currentPosition, currentPosition + componentLength); + + int r = (int) (Integer.parseInt(rString, 16) * mult); + int g = (int) (Integer.parseInt(gString, 16) * mult); + int b = (int) (Integer.parseInt(bString, 16) * mult); + return 0xFF << 24 | r << 16 | g << 8 | b; + } catch (NumberFormatException | IndexOutOfBoundsException e) { + return 0; } + } - /** Reset a particular indexed color with the default color from the color theme. */ - public void reset(int index) { - mCurrentColors[index] = COLOR_SCHEME.mDefaultColors[index]; - } - - /** Reset all indexed colors with the default color from the color theme. */ - public void reset() { - reset(COLOR_SCHEME); - } - - public void reset(TerminalColorScheme colorScheme) { - System.arraycopy(colorScheme.mDefaultColors, 0, mCurrentColors, 0, TextStyle.NUM_INDEXED_COLORS); - } - - /** - * Parse color according to http://manpages.ubuntu.com/manpages/intrepid/man3/XQueryColor.3.html - *

- * Highest bit is set if successful, so return value is 0xFF${R}${G}${B}. Return 0 if failed. - */ - public static int parse(String c) { - try { - int skipInitial, skipBetween; - if (c.charAt(0) == '#') { - // #RGB, #RRGGBB, #RRRGGGBBB or #RRRRGGGGBBBB. Most significant bits. - skipInitial = 1; - skipBetween = 0; - } else if (c.startsWith("rgb:")) { - // rgb:// where , , := h | hh | hhh | hhhh. Scaled. - skipInitial = 4; - skipBetween = 1; - } else { - // assume that c is an int - return Integer.parseInt(c); - } - int charsForColors = c.length() - skipInitial - 2 * skipBetween; - if (charsForColors % 3 != 0) return 0; // Unequal lengths. - int componentLength = charsForColors / 3; - double mult = 255 / (Math.pow(2, componentLength * 4) - 1); - - int currentPosition = skipInitial; - String rString = c.substring(currentPosition, currentPosition + componentLength); - currentPosition += componentLength + skipBetween; - String gString = c.substring(currentPosition, currentPosition + componentLength); - currentPosition += componentLength + skipBetween; - String bString = c.substring(currentPosition, currentPosition + componentLength); - - int r = (int) (Integer.parseInt(rString, 16) * mult); - int g = (int) (Integer.parseInt(gString, 16) * mult); - int b = (int) (Integer.parseInt(bString, 16) * mult); - return 0xFF << 24 | r << 16 | g << 8 | b; - } catch (NumberFormatException | IndexOutOfBoundsException e) { - return 0; - } - } - - /** Try parse a color from a text parameter and into a specified index. */ - public void tryParseColor(int intoIndex, String textParameter) { - int c = parse(textParameter); - if (c != 0) mCurrentColors[intoIndex] = c; - } + /** + * Try parse a color from a text parameter and into a specified index. + */ + public void tryParseColor(int intoIndex, String textParameter) { + int c = parse(textParameter); + if (c != 0) mCurrentColors[intoIndex] = c; + } } diff --git a/app/src/main/java/io/neoterm/backend/TerminalEmulator.java b/app/src/main/java/io/neoterm/backend/TerminalEmulator.java index 8ddecfb..83ed39a 100755 --- a/app/src/main/java/io/neoterm/backend/TerminalEmulator.java +++ b/app/src/main/java/io/neoterm/backend/TerminalEmulator.java @@ -29,2478 +29,2478 @@ import java.util.Stack; */ public final class TerminalEmulator { - /** - * Log unknown or unimplemented escape sequences received from the shell process. - */ - private static final boolean LOG_ESCAPE_SEQUENCES = false; + /** + * Log unknown or unimplemented escape sequences received from the shell process. + */ + private static final boolean LOG_ESCAPE_SEQUENCES = false; - public static final int MOUSE_LEFT_BUTTON = 0; + public static final int MOUSE_LEFT_BUTTON = 0; - /** - * Mouse moving while having left mouse button pressed. - */ - public static final int MOUSE_LEFT_BUTTON_MOVED = 32; - public static final int MOUSE_WHEELUP_BUTTON = 64; - public static final int MOUSE_WHEELDOWN_BUTTON = 65; + /** + * Mouse moving while having left mouse button pressed. + */ + public static final int MOUSE_LEFT_BUTTON_MOVED = 32; + public static final int MOUSE_WHEELUP_BUTTON = 64; + public static final int MOUSE_WHEELDOWN_BUTTON = 65; - public static final int CURSOR_STYLE_BLOCK = 0; - public static final int CURSOR_STYLE_UNDERLINE = 1; - public static final int CURSOR_STYLE_BAR = 2; + public static final int CURSOR_STYLE_BLOCK = 0; + public static final int CURSOR_STYLE_UNDERLINE = 1; + public static final int CURSOR_STYLE_BAR = 2; - /** - * Used for invalid data - http://en.wikipedia.org/wiki/Replacement_character#Replacement_character - */ - public static final int UNICODE_REPLACEMENT_CHAR = 0xFFFD; + /** + * Used for invalid data - http://en.wikipedia.org/wiki/Replacement_character#Replacement_character + */ + public static final int UNICODE_REPLACEMENT_CHAR = 0xFFFD; - /** - * Escape processing: Not currently in an escape sequence. - */ - private static final int ESC_NONE = 0; - /** - * Escape processing: Have seen an ESC character - proceed to {@link #doEsc(int)} - */ - private static final int ESC = 1; - /** - * Escape processing: Have seen ESC POUND - */ - private static final int ESC_POUND = 2; - /** - * Escape processing: Have seen ESC and a character-set-select ( char - */ - private static final int ESC_SELECT_LEFT_PAREN = 3; - /** - * Escape processing: Have seen ESC and a character-set-select ) char - */ - private static final int ESC_SELECT_RIGHT_PAREN = 4; - /** - * Escape processing: "ESC [" or CSI (Control Sequence Introducer). - */ - private static final int ESC_CSI = 6; - /** - * Escape processing: ESC [ ? - */ - private static final int ESC_CSI_QUESTIONMARK = 7; - /** - * Escape processing: ESC [ $ - */ - private static final int ESC_CSI_DOLLAR = 8; - /** - * Escape processing: ESC % - */ - private static final int ESC_PERCENT = 9; - /** - * Escape processing: ESC ] (AKA OSC - Operating System Controls) - */ - private static final int ESC_OSC = 10; - /** - * Escape processing: ESC ] (AKA OSC - Operating System Controls) ESC - */ - private static final int ESC_OSC_ESC = 11; - /** - * Escape processing: ESC [ > - */ - private static final int ESC_CSI_BIGGERTHAN = 12; - /** - * Escape procession: "ESC P" or Device Control String (DCS) - */ - private static final int ESC_P = 13; - /** - * Escape processing: CSI > - */ - private static final int ESC_CSI_QUESTIONMARK_ARG_DOLLAR = 14; - /** - * Escape processing: CSI $ARGS ' ' - */ - private static final int ESC_CSI_ARGS_SPACE = 15; - /** - * Escape processing: CSI $ARGS '*' - */ - private static final int ESC_CSI_ARGS_ASTERIX = 16; - /** - * Escape processing: CSI " - */ - private static final int ESC_CSI_DOUBLE_QUOTE = 17; - /** - * Escape processing: CSI ' - */ - private static final int ESC_CSI_SINGLE_QUOTE = 18; - /** - * Escape processing: CSI ! - */ - private static final int ESC_CSI_EXCLAMATION = 19; + /** + * Escape processing: Not currently in an escape sequence. + */ + private static final int ESC_NONE = 0; + /** + * Escape processing: Have seen an ESC character - proceed to {@link #doEsc(int)} + */ + private static final int ESC = 1; + /** + * Escape processing: Have seen ESC POUND + */ + private static final int ESC_POUND = 2; + /** + * Escape processing: Have seen ESC and a character-set-select ( char + */ + private static final int ESC_SELECT_LEFT_PAREN = 3; + /** + * Escape processing: Have seen ESC and a character-set-select ) char + */ + private static final int ESC_SELECT_RIGHT_PAREN = 4; + /** + * Escape processing: "ESC [" or CSI (Control Sequence Introducer). + */ + private static final int ESC_CSI = 6; + /** + * Escape processing: ESC [ ? + */ + private static final int ESC_CSI_QUESTIONMARK = 7; + /** + * Escape processing: ESC [ $ + */ + private static final int ESC_CSI_DOLLAR = 8; + /** + * Escape processing: ESC % + */ + private static final int ESC_PERCENT = 9; + /** + * Escape processing: ESC ] (AKA OSC - Operating System Controls) + */ + private static final int ESC_OSC = 10; + /** + * Escape processing: ESC ] (AKA OSC - Operating System Controls) ESC + */ + private static final int ESC_OSC_ESC = 11; + /** + * Escape processing: ESC [ > + */ + private static final int ESC_CSI_BIGGERTHAN = 12; + /** + * Escape procession: "ESC P" or Device Control String (DCS) + */ + private static final int ESC_P = 13; + /** + * Escape processing: CSI > + */ + private static final int ESC_CSI_QUESTIONMARK_ARG_DOLLAR = 14; + /** + * Escape processing: CSI $ARGS ' ' + */ + private static final int ESC_CSI_ARGS_SPACE = 15; + /** + * Escape processing: CSI $ARGS '*' + */ + private static final int ESC_CSI_ARGS_ASTERIX = 16; + /** + * Escape processing: CSI " + */ + private static final int ESC_CSI_DOUBLE_QUOTE = 17; + /** + * Escape processing: CSI ' + */ + private static final int ESC_CSI_SINGLE_QUOTE = 18; + /** + * Escape processing: CSI ! + */ + private static final int ESC_CSI_EXCLAMATION = 19; - /** - * The number of parameter arguments. This name comes from the ANSI standard for terminal escape codes. - */ - private static final int MAX_ESCAPE_PARAMETERS = 16; + /** + * The number of parameter arguments. This name comes from the ANSI standard for terminal escape codes. + */ + private static final int MAX_ESCAPE_PARAMETERS = 16; - /** - * Needs to be large enough to contain reasonable OSC 52 pastes. - */ - private static final int MAX_OSC_STRING_LENGTH = 8192; + /** + * Needs to be large enough to contain reasonable OSC 52 pastes. + */ + private static final int MAX_OSC_STRING_LENGTH = 8192; - /** - * DECSET 1 - application cursorColor keys. - */ - private static final int DECSET_BIT_APPLICATION_CURSOR_KEYS = 1; - private static final int DECSET_BIT_REVERSE_VIDEO = 1 << 1; - /** - * http://www.vt100.net/docs/vt510-rm/DECOM: "When DECOM is set, the home cursorColor position is at the upper-left - * corner of the screen, within the margins. The starting point for line numbers depends on the current top margin - * setting. The cursorColor cannot move outside of the margins. When DECOM is reset, the home cursorColor position is at the - * upper-left corner of the screen. The starting point for line numbers is independent of the margins. The cursorColor - * can move outside of the margins." - */ - private static final int DECSET_BIT_ORIGIN_MODE = 1 << 2; - /** - * http://www.vt100.net/docs/vt510-rm/DECAWM: "If the DECAWM function is set, then graphic characters received when - * the cursorColor is at the right border of the page appear at the beginning of the next line. Any text on the page - * scrolls up if the cursorColor is at the end of the scrolling region. If the DECAWM function is reset, then graphic - * characters received when the cursorColor is at the right border of the page replace characters already on the page." - */ - private static final int DECSET_BIT_AUTOWRAP = 1 << 3; - /** - * DECSET 25 - if the cursorColor should be visible, {@link #isShowingCursor()}. - */ - private static final int DECSET_BIT_SHOWING_CURSOR = 1 << 4; - private static final int DECSET_BIT_APPLICATION_KEYPAD = 1 << 5; - /** - * DECSET 1000 - if to report mouse press&release events. - */ - private static final int DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE = 1 << 6; - /** - * DECSET 1002 - like 1000, but report moving mouse while pressed. - */ - private static final int DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT = 1 << 7; - /** - * DECSET 1004 - NOT implemented. - */ - private static final int DECSET_BIT_SEND_FOCUS_EVENTS = 1 << 8; - /** - * DECSET 1006 - SGR-like mouse protocol (the modern sane choice). - */ - private static final int DECSET_BIT_MOUSE_PROTOCOL_SGR = 1 << 9; - /** - * DECSET 2004 - see {@link #paste(String)} - */ - private static final int DECSET_BIT_BRACKETED_PASTE_MODE = 1 << 10; - /** - * Toggled with DECLRMM - http://www.vt100.net/docs/vt510-rm/DECLRMM - */ - private static final int DECSET_BIT_LEFTRIGHT_MARGIN_MODE = 1 << 11; - /** - * Not really DECSET bit... - http://www.vt100.net/docs/vt510-rm/DECSACE - */ - private static final int DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE = 1 << 12; + /** + * DECSET 1 - application cursorColor keys. + */ + private static final int DECSET_BIT_APPLICATION_CURSOR_KEYS = 1; + private static final int DECSET_BIT_REVERSE_VIDEO = 1 << 1; + /** + * http://www.vt100.net/docs/vt510-rm/DECOM: "When DECOM is set, the home cursorColor position is at the upper-left + * corner of the screen, within the margins. The starting point for line numbers depends on the current top margin + * setting. The cursorColor cannot move outside of the margins. When DECOM is reset, the home cursorColor position is at the + * upper-left corner of the screen. The starting point for line numbers is independent of the margins. The cursorColor + * can move outside of the margins." + */ + private static final int DECSET_BIT_ORIGIN_MODE = 1 << 2; + /** + * http://www.vt100.net/docs/vt510-rm/DECAWM: "If the DECAWM function is set, then graphic characters received when + * the cursorColor is at the right border of the page appear at the beginning of the next line. Any text on the page + * scrolls up if the cursorColor is at the end of the scrolling region. If the DECAWM function is reset, then graphic + * characters received when the cursorColor is at the right border of the page replace characters already on the page." + */ + private static final int DECSET_BIT_AUTOWRAP = 1 << 3; + /** + * DECSET 25 - if the cursorColor should be visible, {@link #isShowingCursor()}. + */ + private static final int DECSET_BIT_SHOWING_CURSOR = 1 << 4; + private static final int DECSET_BIT_APPLICATION_KEYPAD = 1 << 5; + /** + * DECSET 1000 - if to report mouse press&release events. + */ + private static final int DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE = 1 << 6; + /** + * DECSET 1002 - like 1000, but report moving mouse while pressed. + */ + private static final int DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT = 1 << 7; + /** + * DECSET 1004 - NOT implemented. + */ + private static final int DECSET_BIT_SEND_FOCUS_EVENTS = 1 << 8; + /** + * DECSET 1006 - SGR-like mouse protocol (the modern sane choice). + */ + private static final int DECSET_BIT_MOUSE_PROTOCOL_SGR = 1 << 9; + /** + * DECSET 2004 - see {@link #paste(String)} + */ + private static final int DECSET_BIT_BRACKETED_PASTE_MODE = 1 << 10; + /** + * Toggled with DECLRMM - http://www.vt100.net/docs/vt510-rm/DECLRMM + */ + private static final int DECSET_BIT_LEFTRIGHT_MARGIN_MODE = 1 << 11; + /** + * Not really DECSET bit... - http://www.vt100.net/docs/vt510-rm/DECSACE + */ + private static final int DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE = 1 << 12; - private String mTitle; - private final Stack mTitleStack = new Stack<>(); + private String mTitle; + private final Stack mTitleStack = new Stack<>(); - /** - * The cursorColor position. Between (0,0) and (mRows-1, mColumns-1). - */ - private int mCursorRow, mCursorCol; + /** + * The cursorColor position. Between (0,0) and (mRows-1, mColumns-1). + */ + private int mCursorRow, mCursorCol; - private int mCursorStyle = CURSOR_STYLE_BLOCK; + private int mCursorStyle = CURSOR_STYLE_BLOCK; - /** - * The number of character rows and columns in the terminal screen. - */ - public int mRows, mColumns; + /** + * The number of character rows and columns in the terminal screen. + */ + public int mRows, mColumns; - /** - * The normal screen buffer. Stores the characters that appear on the screen of the emulated terminal. - */ - private final TerminalBuffer mMainBuffer; - /** - * The alternate screen buffer, exactly as large as the display and contains no additional saved lines (so that when - * the alternate screen buffer is active, you cannot scroll back to view saved lines). - *

- * See http://www.xfree86.org/current/ctlseqs.html#The%20Alternate%20Screen%20Buffer - */ - final TerminalBuffer mAltBuffer; - /** - * The current screen buffer, pointing at either {@link #mMainBuffer} or {@link #mAltBuffer}. - */ - private TerminalBuffer mScreen; + /** + * The normal screen buffer. Stores the characters that appear on the screen of the emulated terminal. + */ + private final TerminalBuffer mMainBuffer; + /** + * The alternate screen buffer, exactly as large as the display and contains no additional saved lines (so that when + * the alternate screen buffer is active, you cannot scroll back to view saved lines). + *

+ * See http://www.xfree86.org/current/ctlseqs.html#The%20Alternate%20Screen%20Buffer + */ + final TerminalBuffer mAltBuffer; + /** + * The current screen buffer, pointing at either {@link #mMainBuffer} or {@link #mAltBuffer}. + */ + private TerminalBuffer mScreen; - /** - * The terminal session this emulator is bound to. - */ - private final TerminalOutput mSession; + /** + * The terminal session this emulator is bound to. + */ + private final TerminalOutput mSession; - /** - * Keeps track of the current argument of the current escape sequence. Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. - */ - private int mArgIndex; - /** - * Holds the arguments of the current escape sequence. - */ - private final int[] mArgs = new int[MAX_ESCAPE_PARAMETERS]; + /** + * Keeps track of the current argument of the current escape sequence. Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. + */ + private int mArgIndex; + /** + * Holds the arguments of the current escape sequence. + */ + private final int[] mArgs = new int[MAX_ESCAPE_PARAMETERS]; - /** - * Holds OSC and device control arguments, which can be strings. - */ - private final StringBuilder mOSCOrDeviceControlArgs = new StringBuilder(); + /** + * Holds OSC and device control arguments, which can be strings. + */ + private final StringBuilder mOSCOrDeviceControlArgs = new StringBuilder(); - /** - * True if the current escape sequence should continue, false if the current escape sequence should be terminated. - * Used when parsing a single character. - */ - private boolean mContinueSequence; + /** + * True if the current escape sequence should continue, false if the current escape sequence should be terminated. + * Used when parsing a single character. + */ + private boolean mContinueSequence; - /** - * The current state of the escape sequence state machine. One of the ESC_* constants. - */ - private int mEscapeState; + /** + * The current state of the escape sequence state machine. One of the ESC_* constants. + */ + private int mEscapeState; - private final SavedScreenState mSavedStateMain = new SavedScreenState(); - private final SavedScreenState mSavedStateAlt = new SavedScreenState(); + private final SavedScreenState mSavedStateMain = new SavedScreenState(); + private final SavedScreenState mSavedStateAlt = new SavedScreenState(); - /** - * http://www.vt100.net/docs/vt102-ug/table5-15.html - */ - private boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true; + /** + * http://www.vt100.net/docs/vt102-ug/table5-15.html + */ + private boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true; - /** - * @see TerminalEmulator#mapDecSetBitToInternalBit(int) - */ - private int mCurrentDecSetFlags, mSavedDecSetFlags; + /** + * @see TerminalEmulator#mapDecSetBitToInternalBit(int) + */ + private int mCurrentDecSetFlags, mSavedDecSetFlags; - /** - * If insert mode (as opposed to replace mode) is active. In insert mode new characters are inserted, pushing - * existing text to the right. Characters moved past the right margin are lost. - */ - private boolean mInsertMode; + /** + * If insert mode (as opposed to replace mode) is active. In insert mode new characters are inserted, pushing + * existing text to the right. Characters moved past the right margin are lost. + */ + private boolean mInsertMode; - /** - * An array of menu_main stops. mTabStop[i] is true if there is a menu_main stop set for column i. - */ - private boolean[] mTabStop; + /** + * An array of menu_main stops. mTabStop[i] is true if there is a menu_main stop set for column i. + */ + private boolean[] mTabStop; - /** - * Top margin of screen for scrolling ranges from 0 to mRows-2. Bottom margin ranges from mTopMargin + 2 to mRows - * (Defines the first row after the scrolling region). Left/right margin in [0, mColumns]. - */ - private int mTopMargin, mBottomMargin, mLeftMargin, mRightMargin; + /** + * Top margin of screen for scrolling ranges from 0 to mRows-2. Bottom margin ranges from mTopMargin + 2 to mRows + * (Defines the first row after the scrolling region). Left/right margin in [0, mColumns]. + */ + private int mTopMargin, mBottomMargin, mLeftMargin, mRightMargin; - /** - * If the next character to be emitted will be automatically wrapped to the next line. Used to disambiguate the case - * where the cursorColor is positioned on the last column (mColumns-1). When standing there, a written character will be - * output in the last column, the cursorColor not moving but this flag will be set. When outputting another character - * this will move to the next line. - */ - private boolean mAboutToAutoWrap; + /** + * If the next character to be emitted will be automatically wrapped to the next line. Used to disambiguate the case + * where the cursorColor is positioned on the last column (mColumns-1). When standing there, a written character will be + * output in the last column, the cursorColor not moving but this flag will be set. When outputting another character + * this will move to the next line. + */ + private boolean mAboutToAutoWrap; - /** - * Current foregroundColor and backgroundColor colors. Can either be a color index in [0,259] or a truecolor (24-bit) value. - * For a 24-bit value the top byte (0xff000000) is set. - * - * @see TextStyle - */ - int mForeColor, mBackColor; + /** + * Current foregroundColor and backgroundColor colors. Can either be a color index in [0,259] or a truecolor (24-bit) value. + * For a 24-bit value the top byte (0xff000000) is set. + * + * @see TextStyle + */ + int mForeColor, mBackColor; - /** - * Current {@link TextStyle} effect. - */ - private int mEffect; + /** + * Current {@link TextStyle} effect. + */ + private int mEffect; - /** - * The number of scrolled lines since last calling {@link #clearScrollCounter()}. Used for moving selection up along - * with the scrolling text. - */ - private int mScrollCounter = 0; + /** + * The number of scrolled lines since last calling {@link #clearScrollCounter()}. Used for moving selection up along + * with the scrolling text. + */ + private int mScrollCounter = 0; - private byte mUtf8ToFollow, mUtf8Index; - private final byte[] mUtf8InputBuffer = new byte[4]; - private int mLastEmittedCodePoint = -1; + private byte mUtf8ToFollow, mUtf8Index; + private final byte[] mUtf8InputBuffer = new byte[4]; + private int mLastEmittedCodePoint = -1; - public final TerminalColors mColors = new TerminalColors(); + public final TerminalColors mColors = new TerminalColors(); - private boolean isDecsetInternalBitSet(int bit) { - return (mCurrentDecSetFlags & bit) != 0; + private boolean isDecsetInternalBitSet(int bit) { + return (mCurrentDecSetFlags & bit) != 0; + } + + private void setDecsetinternalBit(int internalBit, boolean set) { + if (set) { + // The mouse modes are mutually exclusive. + if (internalBit == DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE) { + setDecsetinternalBit(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT, false); + } else if (internalBit == DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT) { + setDecsetinternalBit(DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE, false); + } + } + if (set) { + mCurrentDecSetFlags |= internalBit; + } else { + mCurrentDecSetFlags &= ~internalBit; + } + } + + static int mapDecSetBitToInternalBit(int decsetBit) { + switch (decsetBit) { + case 1: + return DECSET_BIT_APPLICATION_CURSOR_KEYS; + case 5: + return DECSET_BIT_REVERSE_VIDEO; + case 6: + return DECSET_BIT_ORIGIN_MODE; + case 7: + return DECSET_BIT_AUTOWRAP; + case 25: + return DECSET_BIT_SHOWING_CURSOR; + case 66: + return DECSET_BIT_APPLICATION_KEYPAD; + case 69: + return DECSET_BIT_LEFTRIGHT_MARGIN_MODE; + case 1000: + return DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE; + case 1002: + return DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT; + case 1004: + return DECSET_BIT_SEND_FOCUS_EVENTS; + case 1006: + return DECSET_BIT_MOUSE_PROTOCOL_SGR; + case 2004: + return DECSET_BIT_BRACKETED_PASTE_MODE; + default: + return -1; + // throw new IllegalArgumentException("Unsupported decset: " + decsetBit); + } + } + + public TerminalEmulator(TerminalOutput session, int columns, int rows, int transcriptRows) { + mSession = session; + mScreen = mMainBuffer = new TerminalBuffer(columns, transcriptRows, rows); + mAltBuffer = new TerminalBuffer(columns, rows, rows); + mRows = rows; + mColumns = columns; + mTabStop = new boolean[mColumns]; + reset(); + } + + public TerminalBuffer getScreen() { + return mScreen; + } + + public boolean isAlternateBufferActive() { + return mScreen == mAltBuffer; + } + + /** + * @param mouseButton one of the MOUSE_* constants of this class. + */ + public void sendMouseEvent(int mouseButton, int column, int row, boolean pressed) { + if (column < 1) column = 1; + if (column > mColumns) column = mColumns; + if (row < 1) row = 1; + if (row > mRows) row = mRows; + + if (mouseButton == MOUSE_LEFT_BUTTON_MOVED && !isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT)) { + // Do not send tracking. + } else if (isDecsetInternalBitSet(DECSET_BIT_MOUSE_PROTOCOL_SGR)) { + mSession.write(String.format("\033[<%d;%d;%d" + (pressed ? 'M' : 'm'), mouseButton, column, row)); + } else { + mouseButton = pressed ? mouseButton : 3; // 3 for release of all buttons. + // Clip to screen, and clip to the limits of 8-bit data. + boolean outOfBounds = column > 255 - 32 || row > 255 - 32; + if (!outOfBounds) { + byte[] data = {'\033', '[', 'M', (byte) (32 + mouseButton), (byte) (32 + column), (byte) (32 + row)}; + mSession.write(data, 0, data.length); + } + } + } + + public void resize(int columns, int rows) { + if (mRows == rows && mColumns == columns) { + return; + } else if (columns < 2 || rows < 2) { + throw new IllegalArgumentException("rows=" + rows + ", columns=" + columns); } - private void setDecsetinternalBit(int internalBit, boolean set) { - if (set) { - // The mouse modes are mutually exclusive. - if (internalBit == DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE) { - setDecsetinternalBit(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT, false); - } else if (internalBit == DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT) { - setDecsetinternalBit(DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE, false); + if (mRows != rows) { + mRows = rows; + mTopMargin = 0; + mBottomMargin = mRows; + } + if (mColumns != columns) { + int oldColumns = mColumns; + mColumns = columns; + boolean[] oldTabStop = mTabStop; + mTabStop = new boolean[mColumns]; + setDefaultTabStops(); + int toTransfer = Math.min(oldColumns, columns); + System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer); + mLeftMargin = 0; + mRightMargin = mColumns; + } + + resizeScreen(); + } + + private void resizeScreen() { + final int[] cursor = {mCursorCol, mCursorRow}; + int newTotalRows = (mScreen == mAltBuffer) ? mRows : mMainBuffer.mTotalRows; + mScreen.resize(mColumns, mRows, newTotalRows, cursor, getStyle(), isAlternateBufferActive()); + mCursorCol = cursor[0]; + mCursorRow = cursor[1]; + } + + public int getCursorRow() { + return mCursorRow; + } + + public int getCursorCol() { + return mCursorCol; + } + + /** + * {@link #CURSOR_STYLE_BAR}, {@link #CURSOR_STYLE_BLOCK} or {@link #CURSOR_STYLE_UNDERLINE} + */ + public int getCursorStyle() { + return mCursorStyle; + } + + public boolean isReverseVideo() { + return isDecsetInternalBitSet(DECSET_BIT_REVERSE_VIDEO); + } + + public boolean isShowingCursor() { + return isDecsetInternalBitSet(DECSET_BIT_SHOWING_CURSOR); + } + + public boolean isKeypadApplicationMode() { + return isDecsetInternalBitSet(DECSET_BIT_APPLICATION_KEYPAD); + } + + public boolean isCursorKeysApplicationMode() { + return isDecsetInternalBitSet(DECSET_BIT_APPLICATION_CURSOR_KEYS); + } + + /** + * If mouse events are being sent as escape codes to the terminal. + */ + public boolean isMouseTrackingActive() { + return isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE) || isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT); + } + + private void setDefaultTabStops() { + for (int i = 0; i < mColumns; i++) + mTabStop[i] = (i & 7) == 0 && i != 0; + } + + /** + * Accept bytes (typically from the pseudo-teletype) and process them. + * + * @param buffer a byte array containing the bytes to be processed + * @param length the number of bytes in the array to process + */ + public void append(byte[] buffer, int length) { + for (int i = 0; i < length; i++) + processByte(buffer[i]); + } + + private void processByte(byte byteToProcess) { + if (mUtf8ToFollow > 0) { + if ((byteToProcess & 0b11000000) == 0b10000000) { + // 10xxxxxx, a continuation byte. + mUtf8InputBuffer[mUtf8Index++] = byteToProcess; + if (--mUtf8ToFollow == 0) { + byte firstByteMask = (byte) (mUtf8Index == 2 ? 0b00011111 : (mUtf8Index == 3 ? 0b00001111 : 0b00000111)); + int codePoint = (mUtf8InputBuffer[0] & firstByteMask); + for (int i = 1; i < mUtf8Index; i++) + codePoint = ((codePoint << 6) | (mUtf8InputBuffer[i] & 0b00111111)); + if (((codePoint <= 0b1111111) && mUtf8Index > 1) || (codePoint < 0b11111111111 && mUtf8Index > 2) + || (codePoint < 0b1111111111111111 && mUtf8Index > 3)) { + // Overlong encoding. + codePoint = UNICODE_REPLACEMENT_CHAR; + } + + mUtf8Index = mUtf8ToFollow = 0; + + if (codePoint >= 0x80 && codePoint <= 0x9F) { + // Sequence decoded to a C1 control character which is the same as escape followed by + // ((code & 0x7F) + 0x40). + // processCodePoint(/* escape (hexadecimal=0x1B, octal=033): */27); + // processCodePoint((codePoint & 0x7F) + 0x40); + + // Sequence decoded to a C1 control character which we ignore. They are + // not used nowadays and increases the risk of messing up the terminal state + // on binary input. XTerm does not allow them in utf-8: + // "It is not possible to use a C1 control obtained from decoding the + // UTF-8 text" - http://invisible-island.net/xterm/ctlseqs/ctlseqs.htm + } else { + switch (Character.getType(codePoint)) { + case Character.UNASSIGNED: + case Character.SURROGATE: + codePoint = UNICODE_REPLACEMENT_CHAR; } + processCodePoint(codePoint); + } } - if (set) { - mCurrentDecSetFlags |= internalBit; + } else { + // Not a UTF-8 continuation byte so replace the entire sequence up to now with the replacement char: + mUtf8Index = mUtf8ToFollow = 0; + emitCodePoint(UNICODE_REPLACEMENT_CHAR); + // The Unicode Standard Version 6.2 – Core Specification + // (http://www.unicode.org/versions/Unicode6.2.0/ch03.pdf): + // "If the converter encounters an ill-formed UTF-8 code unit sequence which starts with a valid first + // byte, but which does not continue with valid successor bytes (see Table 3-7), it must not consume the + // successor bytes as part of the ill-formed subsequence + // whenever those successor bytes themselves constitute part of a well-formed UTF-8 code unit + // subsequence." + processByte(byteToProcess); + } + } else { + if ((byteToProcess & 0b10000000) == 0) { // The leading bit is not set so it is a 7-bit ASCII character. + processCodePoint(byteToProcess); + return; + } else if ((byteToProcess & 0b11100000) == 0b11000000) { // 110xxxxx, a two-byte sequence. + mUtf8ToFollow = 1; + } else if ((byteToProcess & 0b11110000) == 0b11100000) { // 1110xxxx, a three-byte sequence. + mUtf8ToFollow = 2; + } else if ((byteToProcess & 0b11111000) == 0b11110000) { // 11110xxx, a four-byte sequence. + mUtf8ToFollow = 3; + } else { + // Not a valid UTF-8 sequence start, signal invalid data: + processCodePoint(UNICODE_REPLACEMENT_CHAR); + return; + } + mUtf8InputBuffer[mUtf8Index++] = byteToProcess; + } + } + + public void processCodePoint(int b) { + switch (b) { + case 0: // Null character (NUL, ^@). Do nothing. + break; + case 7: // Bell (BEL, ^G, \a). If in an OSC sequence, BEL may terminate a string; otherwise signal bell. + if (mEscapeState == ESC_OSC) + doOsc(b); + else + mSession.onBell(); + break; + case 8: // Backspace (BS, ^H). + if (mLeftMargin == mCursorCol) { + // Jump to previous line if it was auto-wrapped. + int previousRow = mCursorRow - 1; + if (previousRow >= 0 && mScreen.getLineWrap(previousRow)) { + mScreen.clearLineWrap(previousRow); + setCursorRowCol(previousRow, mRightMargin - 1); + } } else { - mCurrentDecSetFlags &= ~internalBit; + setCursorCol(mCursorCol - 1); } - } - - static int mapDecSetBitToInternalBit(int decsetBit) { - switch (decsetBit) { - case 1: - return DECSET_BIT_APPLICATION_CURSOR_KEYS; - case 5: - return DECSET_BIT_REVERSE_VIDEO; - case 6: - return DECSET_BIT_ORIGIN_MODE; - case 7: - return DECSET_BIT_AUTOWRAP; - case 25: - return DECSET_BIT_SHOWING_CURSOR; - case 66: - return DECSET_BIT_APPLICATION_KEYPAD; - case 69: - return DECSET_BIT_LEFTRIGHT_MARGIN_MODE; - case 1000: - return DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE; - case 1002: - return DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT; - case 1004: - return DECSET_BIT_SEND_FOCUS_EVENTS; - case 1006: - return DECSET_BIT_MOUSE_PROTOCOL_SGR; - case 2004: - return DECSET_BIT_BRACKETED_PASTE_MODE; - default: - return -1; - // throw new IllegalArgumentException("Unsupported decset: " + decsetBit); + break; + case 9: // Horizontal menu_main (HT, \t) - move to next menu_main stop, but not past edge of screen + // XXX: Should perhaps use color if writing to new cells. Try with + // printf "\033[41m\tXX\033[0m\n" + // The OSX Terminal.app colors the spaces from the menu_main red, but xterm does not. + // Note that Terminal.app only colors on new cells, in e.g. + // printf "\033[41m\t\r\033[42m\tXX\033[0m\n" + // the first cells are created with a red backgroundColor, but when tabbing over + // them again with a green backgroundColor they are not overwritten. + mCursorCol = nextTabStop(1); + break; + case 10: // Line feed (LF, \n). + case 11: // Vertical menu_main (VT, \v). + case 12: // Form feed (FF, \f). + doLinefeed(); + break; + case 13: // Carriage return (CR, \r). + setCursorCol(mLeftMargin); + break; + case 14: // Shift Out (Ctrl-N, SO) → Switch to Alternate Character Set. This invokes the G1 character set. + mUseLineDrawingUsesG0 = false; + break; + case 15: // Shift In (Ctrl-O, SI) → Switch to Standard Character Set. This invokes the G0 character set. + mUseLineDrawingUsesG0 = true; + break; + case 24: // CAN. + case 26: // SUB. + if (mEscapeState != ESC_NONE) { + // FIXME: What is this?? + mEscapeState = ESC_NONE; + emitCodePoint(127); } - } - - public TerminalEmulator(TerminalOutput session, int columns, int rows, int transcriptRows) { - mSession = session; - mScreen = mMainBuffer = new TerminalBuffer(columns, transcriptRows, rows); - mAltBuffer = new TerminalBuffer(columns, rows, rows); - mRows = rows; - mColumns = columns; - mTabStop = new boolean[mColumns]; - reset(); - } - - public TerminalBuffer getScreen() { - return mScreen; - } - - public boolean isAlternateBufferActive() { - return mScreen == mAltBuffer; - } - - /** - * @param mouseButton one of the MOUSE_* constants of this class. - */ - public void sendMouseEvent(int mouseButton, int column, int row, boolean pressed) { - if (column < 1) column = 1; - if (column > mColumns) column = mColumns; - if (row < 1) row = 1; - if (row > mRows) row = mRows; - - if (mouseButton == MOUSE_LEFT_BUTTON_MOVED && !isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT)) { - // Do not send tracking. - } else if (isDecsetInternalBitSet(DECSET_BIT_MOUSE_PROTOCOL_SGR)) { - mSession.write(String.format("\033[<%d;%d;%d" + (pressed ? 'M' : 'm'), mouseButton, column, row)); + break; + case 27: // ESC + // Starts an escape sequence unless we're parsing a string + if (mEscapeState == ESC_P) { + // XXX: Ignore escape when reading device control sequence, since it may be part of string terminator. + return; + } else if (mEscapeState != ESC_OSC) { + startEscapeSequence(); } else { - mouseButton = pressed ? mouseButton : 3; // 3 for release of all buttons. - // Clip to screen, and clip to the limits of 8-bit data. - boolean outOfBounds = column > 255 - 32 || row > 255 - 32; - if (!outOfBounds) { - byte[] data = {'\033', '[', 'M', (byte) (32 + mouseButton), (byte) (32 + column), (byte) (32 + row)}; - mSession.write(data, 0, data.length); + doOsc(b); + } + break; + default: + mContinueSequence = false; + switch (mEscapeState) { + case ESC_NONE: + if (b >= 32) emitCodePoint(b); + break; + case ESC: + doEsc(b); + break; + case ESC_POUND: + doEscPound(b); + break; + case ESC_SELECT_LEFT_PAREN: // Designate G0 Character Set (ISO 2022, VT100). + mUseLineDrawingG0 = (b == '0'); + break; + case ESC_SELECT_RIGHT_PAREN: // Designate G1 Character Set (ISO 2022, VT100). + mUseLineDrawingG1 = (b == '0'); + break; + case ESC_CSI: + doCsi(b); + break; + case ESC_CSI_EXCLAMATION: + if (b == 'p') { // Soft terminal reset (DECSTR, http://vt100.net/docs/vt510-rm/DECSTR). + reset(); + } else { + unknownSequence(b); } + break; + case ESC_CSI_QUESTIONMARK: + doCsiQuestionMark(b); + break; + case ESC_CSI_BIGGERTHAN: + doCsiBiggerThan(b); + break; + case ESC_CSI_DOLLAR: + boolean originMode = isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE); + int effectiveTopMargin = originMode ? mTopMargin : 0; + int effectiveBottomMargin = originMode ? mBottomMargin : mRows; + int effectiveLeftMargin = originMode ? mLeftMargin : 0; + int effectiveRightMargin = originMode ? mRightMargin : mColumns; + switch (b) { + case 'v': // ${CSI}${SRC_TOP}${SRC_LEFT}${SRC_BOTTOM}${SRC_RIGHT}${SRC_PAGE}${DST_TOP}${DST_LEFT}${DST_PAGE}$v" + // Copy rectangular area (DECCRA - http://vt100.net/docs/vt510-rm/DECCRA): + // "If Pbs is greater than Pts, or Pls is greater than Prs, the terminal ignores DECCRA. + // The coordinates of the rectangular area are affected by the setting of origin mode (DECOM). + // DECCRA is not affected by the page margins. + // The copied text takes on the line attributes of the destination area. + // If the value of Pt, Pl, Pb, or Pr exceeds the width or height of the active page, then the value + // is treated as the width or height of that page. + // If the destination area is partially off the page, then DECCRA clips the off-page data. + // DECCRA does not change the active cursorColor position." + int topSource = Math.min(getArg(0, 1, true) - 1 + effectiveTopMargin, mRows); + int leftSource = Math.min(getArg(1, 1, true) - 1 + effectiveLeftMargin, mColumns); + // Inclusive, so do not subtract one: + int bottomSource = Math.min(Math.max(getArg(2, mRows, true) + effectiveTopMargin, topSource), mRows); + int rightSource = Math.min(Math.max(getArg(3, mColumns, true) + effectiveLeftMargin, leftSource), mColumns); + // int sourcePage = getArg(4, 1, true); + int destionationTop = Math.min(getArg(5, 1, true) - 1 + effectiveTopMargin, mRows); + int destinationLeft = Math.min(getArg(6, 1, true) - 1 + effectiveLeftMargin, mColumns); + // int destinationPage = getArg(7, 1, true); + int heightToCopy = Math.min(mRows - destionationTop, bottomSource - topSource); + int widthToCopy = Math.min(mColumns - destinationLeft, rightSource - leftSource); + mScreen.blockCopy(leftSource, topSource, widthToCopy, heightToCopy, destinationLeft, destionationTop); + break; + case '{': // ${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${" + // Selective erase rectangular area (DECSERA - http://www.vt100.net/docs/vt510-rm/DECSERA). + case 'x': // ${CSI}${CHAR};${TOP}${LEFT}${BOTTOM}${RIGHT}$x" + // Fill rectangular area (DECFRA - http://www.vt100.net/docs/vt510-rm/DECFRA). + case 'z': // ${CSI}$${TOP}${LEFT}${BOTTOM}${RIGHT}$z" + // Erase rectangular area (DECERA - http://www.vt100.net/docs/vt510-rm/DECERA). + boolean erase = b != 'x'; + boolean selective = b == '{'; + // Only DECSERA keeps visual attributes, DECERA does not: + boolean keepVisualAttributes = erase && selective; + int argIndex = 0; + int fillChar = erase ? ' ' : getArg(argIndex++, -1, true); + // "Pch can be any value from 32 to 126 or from 160 to 255. If Pch is not in this range, then the + // terminal ignores the DECFRA command": + if ((fillChar >= 32 && fillChar <= 126) || (fillChar >= 160 && fillChar <= 255)) { + // "If the value of Pt, Pl, Pb, or Pr exceeds the width or height of the active page, the value + // is treated as the width or height of that page." + int top = Math.min(getArg(argIndex++, 1, true) + effectiveTopMargin, effectiveBottomMargin + 1); + int left = Math.min(getArg(argIndex++, 1, true) + effectiveLeftMargin, effectiveRightMargin + 1); + int bottom = Math.min(getArg(argIndex++, mRows, true) + effectiveTopMargin, effectiveBottomMargin); + int right = Math.min(getArg(argIndex, mColumns, true) + effectiveLeftMargin, effectiveRightMargin); + long style = getStyle(); + for (int row = top - 1; row < bottom; row++) + for (int col = left - 1; col < right; col++) + if (!selective || (TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0) + mScreen.setChar(col, row, fillChar, keepVisualAttributes ? mScreen.getStyleAt(row, col) : style); + } + break; + case 'r': // "${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${ATTRIBUTES}$r" + // Change attributes in rectangular area (DECCARA - http://vt100.net/docs/vt510-rm/DECCARA). + case 't': // "${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${ATTRIBUTES}$t" + // Reverse attributes in rectangular area (DECRARA - http://www.vt100.net/docs/vt510-rm/DECRARA). + boolean reverse = b == 't'; + // FIXME: "coordinates of the rectangular area are affected by the setting of origin mode (DECOM)". + int top = Math.min(getArg(0, 1, true) - 1, effectiveBottomMargin) + effectiveTopMargin; + int left = Math.min(getArg(1, 1, true) - 1, effectiveRightMargin) + effectiveLeftMargin; + int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin; + int right = Math.min(getArg(3, mColumns, true) + 1, effectiveRightMargin - 1) + effectiveLeftMargin; + if (mArgIndex >= 4) { + if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; + for (int i = 4; i <= mArgIndex; i++) { + int bits = 0; + boolean setOrClear = true; // True if setting, false if clearing. + switch (getArg(i, 0, false)) { + case 0: // Attributes off (no bold, no underline, no blink, positive image). + bits = (TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE | TextStyle.CHARACTER_ATTRIBUTE_BLINK + | TextStyle.CHARACTER_ATTRIBUTE_INVERSE); + if (!reverse) setOrClear = false; + break; + case 1: // Bold. + bits = TextStyle.CHARACTER_ATTRIBUTE_BOLD; + break; + case 4: // Underline. + bits = TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; + break; + case 5: // Blink. + bits = TextStyle.CHARACTER_ATTRIBUTE_BLINK; + break; + case 7: // Negative image. + bits = TextStyle.CHARACTER_ATTRIBUTE_INVERSE; + break; + case 22: // No bold. + bits = TextStyle.CHARACTER_ATTRIBUTE_BOLD; + setOrClear = false; + break; + case 24: // No underline. + bits = TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; + setOrClear = false; + break; + case 25: // No blink. + bits = TextStyle.CHARACTER_ATTRIBUTE_BLINK; + setOrClear = false; + break; + case 27: // Positive image. + bits = TextStyle.CHARACTER_ATTRIBUTE_INVERSE; + setOrClear = false; + break; + } + if (reverse && !setOrClear) { + // Reverse attributes in rectangular area ignores non-(1,4,5,7) bits. + } else { + mScreen.setOrClearEffect(bits, setOrClear, reverse, isDecsetInternalBitSet(DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE), + effectiveLeftMargin, effectiveRightMargin, top, left, bottom, right); + } + } + } else { + // Do nothing. + } + break; + default: + unknownSequence(b); + } + break; + case ESC_CSI_DOUBLE_QUOTE: + if (b == 'q') { + // http://www.vt100.net/docs/vt510-rm/DECSCA + int arg = getArg0(0); + if (arg == 0 || arg == 2) { + // DECSED and DECSEL can erase characters. + mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_PROTECTED; + } else if (arg == 1) { + // DECSED and DECSEL cannot erase characters. + mEffect |= TextStyle.CHARACTER_ATTRIBUTE_PROTECTED; + } else { + unknownSequence(b); + } + } else { + unknownSequence(b); + } + break; + case ESC_CSI_SINGLE_QUOTE: + if (b == '}') { // Insert Ps Column(s) (default = 1) (DECIC), VT420 and up. + int columnsAfterCursor = mRightMargin - mCursorCol; + int columnsToInsert = Math.min(getArg0(1), columnsAfterCursor); + int columnsToMove = columnsAfterCursor - columnsToInsert; + mScreen.blockCopy(mCursorCol, 0, columnsToMove, mRows, mCursorCol + columnsToInsert, 0); + blockClear(mCursorCol, 0, columnsToInsert, mRows); + } else if (b == '~') { // Delete Ps Column(s) (default = 1) (DECDC), VT420 and up. + int columnsAfterCursor = mRightMargin - mCursorCol; + int columnsToDelete = Math.min(getArg0(1), columnsAfterCursor); + int columnsToMove = columnsAfterCursor - columnsToDelete; + mScreen.blockCopy(mCursorCol + columnsToDelete, 0, columnsToMove, mRows, mCursorCol, 0); + blockClear(mCursorRow + columnsToMove, 0, columnsToDelete, mRows); + } else { + unknownSequence(b); + } + break; + case ESC_PERCENT: + break; + case ESC_OSC: + doOsc(b); + break; + case ESC_OSC_ESC: + doOscEsc(b); + break; + case ESC_P: + doDeviceControl(b); + break; + case ESC_CSI_QUESTIONMARK_ARG_DOLLAR: + if (b == 'p') { + // Request DEC private mode (DECRQM). + int mode = getArg0(0); + int value; + if (mode == 47 || mode == 1047 || mode == 1049) { + // This state is carried by mScreen pointer. + value = (mScreen == mAltBuffer) ? 1 : 2; + } else { + int internalBit = mapDecSetBitToInternalBit(mode); + if (internalBit == -1) { + value = isDecsetInternalBitSet(internalBit) ? 1 : 2; // 1=set, 2=reset. + } else { + Log.e(EmulatorDebug.LOG_TAG, "Got DECRQM for unrecognized private DEC mode=" + mode); + value = 0; // 0=not recognized, 3=permanently set, 4=permanently reset + } + } + mSession.write(String.format(Locale.US, "\033[?%d;%d$y", mode, value)); + } else { + unknownSequence(b); + } + break; + case ESC_CSI_ARGS_SPACE: + int arg = getArg0(0); + switch (b) { + case 'q': // "${CSI}${STYLE} q" - set cursorColor style (http://www.vt100.net/docs/vt510-rm/DECSCUSR). + switch (arg) { + case 0: // Blinking block. + case 1: // Blinking block. + case 2: // Steady block. + mCursorStyle = CURSOR_STYLE_BLOCK; + break; + case 3: // Blinking underline. + case 4: // Steady underline. + mCursorStyle = CURSOR_STYLE_UNDERLINE; + break; + case 5: // Blinking bar (xterm addition). + case 6: // Steady bar (xterm addition). + mCursorStyle = CURSOR_STYLE_BAR; + break; + } + break; + case 't': + case 'u': + // Set margin-bell volume - ignore. + break; + default: + unknownSequence(b); + } + break; + case ESC_CSI_ARGS_ASTERIX: + int attributeChangeExtent = getArg0(0); + if (b == 'x' && (attributeChangeExtent >= 0 && attributeChangeExtent <= 2)) { + // Select attribute change extent (DECSACE - http://www.vt100.net/docs/vt510-rm/DECSACE). + setDecsetinternalBit(DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE, attributeChangeExtent == 2); + } else { + unknownSequence(b); + } + break; + default: + unknownSequence(b); + break; + } + if (!mContinueSequence) mEscapeState = ESC_NONE; + break; + } + } + + /** + * When in {@link #ESC_P} ("device control") sequence. + */ + private void doDeviceControl(int b) { + switch (b) { + case (byte) '\\': // End of ESC \ string Terminator + { + String dcs = mOSCOrDeviceControlArgs.toString(); + // DCS $ q P t ST. Request Status String (DECRQSS) + if (dcs.startsWith("$q")) { + if (dcs.equals("$q\"p")) { + // DECSCL, conformance level, http://www.vt100.net/docs/vt510-rm/DECSCL: + String csiString = "64;1\"p"; + mSession.write("\033P1$r" + csiString + "\033\\"); + } else { + finishSequenceAndLogError("Unrecognized DECRQSS string: '" + dcs + "'"); + } + } else if (dcs.startsWith("+q")) { + // Request Termcap/Terminfo String. The string following the "q" is a list of names encoded in + // hexadecimal (2 digits per character) separated by ; which correspond to termcap or terminfo key + // names. + // Two special features are also recognized, which are not key names: Co for termcap colors (or colors + // for terminfo colors), and TN for termcap name (or name for terminfo name). + // xterm responds with DCS 1 + r P t ST for valid requests, adding to P t an = , and the value of the + // corresponding string that xterm would send, or DCS 0 + r P t ST for invalid requests. The strings are + // encoded in hexadecimal (2 digits per character). + // Example: + // :kr=\EOC: ks=\E[?1h\E=: ku=\EOA: le=^H:mb=\E[5m:md=\E[1m:\ + // where + // kd=down-arrow key + // kl=left-arrow key + // kr=right-arrow key + // ku=up-arrow key + // #2=key_shome, "shifted home" + // #4=key_sleft, "shift arrow left" + // %i=key_sright, "shift arrow right" + // *7=key_send, "shifted end" + // k1=F1 function key + + // Example: Request for ku is "ESC P + q 6 b 7 5 ESC \", where 6b7d=ku in hexadecimal. + // Xterm response in normal cursorColor mode: + // "<27> P 1 + r 6 b 7 5 = 1 B 5 B 4 1" where 0x1B 0x5B 0x41 = 27 91 65 = ESC [ A + // Xterm response in application cursorColor mode: + // "<27> P 1 + r 6 b 7 5 = 1 B 5 B 4 1" where 0x1B 0x4F 0x41 = 27 91 65 = ESC 0 A + + // #4 is "shift arrow left": + // *** Device Control (DCS) for '#4'- 'ESC P + q 23 34 ESC \' + // Response: <27> P 1 + r 2 3 3 4 = 1 B 5 B 3 1 3 B 3 2 4 4 <27> \ + // where 0x1B 0x5B 0x31 0x3B 0x32 0x44 = ESC [ 1 ; 2 D + // which we find in: TermKeyListener.java: KEY_MAP.put(KEYMOD_SHIFT | KEYCODE_DPAD_LEFT, "\033[1;2D"); + + // See http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V40G_HTML/MAN/MAN4/0178____.HTM for what to + // respond, as well as http://www.freebsd.org/cgi/man.cgi?query=termcap&sektion=5#CAPABILITIES for + // the meaning of e.g. "ku", "kd", "kr", "kl" + + for (String part : dcs.substring(2).split(";")) { + if (part.length() % 2 == 0) { + StringBuilder transBuffer = new StringBuilder(); + for (int i = 0; i < part.length(); i += 2) { + char c = (char) Long.decode("0x" + part.charAt(i) + "" + part.charAt(i + 1)).longValue(); + transBuffer.append(c); + } + String trans = transBuffer.toString(); + String responseValue; + switch (trans) { + case "Co": + case "colors": + responseValue = "256"; // Number of colors. + break; + case "TN": + case "name": + responseValue = "xterm"; + break; + default: + responseValue = KeyHandler.getCodeFromTermcap(trans, isDecsetInternalBitSet(DECSET_BIT_APPLICATION_CURSOR_KEYS), + isDecsetInternalBitSet(DECSET_BIT_APPLICATION_KEYPAD)); + break; + } + if (responseValue == null) { + switch (trans) { + case "%1": // Help key - ignore + case "&8": // Undo key - ignore. + break; + default: + Log.w(EmulatorDebug.LOG_TAG, "Unhandled termcap/terminfo name: '" + trans + "'"); + } + // Respond with invalid request: + mSession.write("\033P0+r" + part + "\033\\"); + } else { + StringBuilder hexEncoded = new StringBuilder(); + for (int j = 0; j < responseValue.length(); j++) { + hexEncoded.append(String.format("%02X", (int) responseValue.charAt(j))); + } + mSession.write("\033P1+r" + part + "=" + hexEncoded + "\033\\"); + } + } else { + Log.e(EmulatorDebug.LOG_TAG, "Invalid device termcap/terminfo name of odd length: " + part); + } + } + } else { + if (LOG_ESCAPE_SEQUENCES) + Log.e(EmulatorDebug.LOG_TAG, "Unrecognized device control string: " + dcs); + } + finishSequence(); + } + break; + default: + if (mOSCOrDeviceControlArgs.length() > MAX_OSC_STRING_LENGTH) { + // Too long. + mOSCOrDeviceControlArgs.setLength(0); + finishSequence(); + } else { + mOSCOrDeviceControlArgs.appendCodePoint(b); + continueSequence(mEscapeState); } } + } - public void resize(int columns, int rows) { - if (mRows == rows && mColumns == columns) { + private int nextTabStop(int numTabs) { + for (int i = mCursorCol + 1; i < mColumns; i++) + if (mTabStop[i] && --numTabs == 0) return Math.min(i, mRightMargin); + return mRightMargin - 1; + } + + /** + * Process byte while in the {@link #ESC_CSI_QUESTIONMARK} escape state. + */ + private void doCsiQuestionMark(int b) { + switch (b) { + case 'J': // Selective erase in display (DECSED) - http://www.vt100.net/docs/vt510-rm/DECSED. + case 'K': // Selective erase in line (DECSEL) - http://vt100.net/docs/vt510-rm/DECSEL. + mAboutToAutoWrap = false; + int fillChar = ' '; + int startCol = -1; + int startRow = -1; + int endCol = -1; + int endRow = -1; + boolean justRow = (b == 'K'); + switch (getArg0(0)) { + case 0: // Erase from the active position to the end, inclusive (default). + startCol = mCursorCol; + startRow = mCursorRow; + endCol = mColumns; + endRow = justRow ? (mCursorRow + 1) : mRows; + break; + case 1: // Erase from start to the active position, inclusive. + startCol = 0; + startRow = justRow ? mCursorRow : 0; + endCol = mCursorCol + 1; + endRow = mCursorRow + 1; + break; + case 2: // Erase all of the display/line. + startCol = 0; + startRow = justRow ? mCursorRow : 0; + endCol = mColumns; + endRow = justRow ? (mCursorRow + 1) : mRows; + break; + default: + unknownSequence(b); + break; + } + long style = getStyle(); + for (int row = startRow; row < endRow; row++) { + for (int col = startCol; col < endCol; col++) { + if ((TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0) + mScreen.setChar(col, row, fillChar, style); + } + } + break; + case 'h': + case 'l': + if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; + for (int i = 0; i <= mArgIndex; i++) + doDecSetOrReset(b == 'h', mArgs[i]); + break; + case 'n': // Device Status Report (DSR, DEC-specific). + switch (getArg0(-1)) { + case 6: + // Extended Cursor Position (DECXCPR - http://www.vt100.net/docs/vt510-rm/DECXCPR). Page=1. + mSession.write(String.format(Locale.US, "\033[?%d;%d;1R", mCursorRow + 1, mCursorCol + 1)); + break; + default: + finishSequence(); return; - } else if (columns < 2 || rows < 2) { - throw new IllegalArgumentException("rows=" + rows + ", columns=" + columns); } - - if (mRows != rows) { - mRows = rows; - mTopMargin = 0; - mBottomMargin = mRows; - } - if (mColumns != columns) { - int oldColumns = mColumns; - mColumns = columns; - boolean[] oldTabStop = mTabStop; - mTabStop = new boolean[mColumns]; - setDefaultTabStops(); - int toTransfer = Math.min(oldColumns, columns); - System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer); - mLeftMargin = 0; - mRightMargin = mColumns; - } - - resizeScreen(); - } - - private void resizeScreen() { - final int[] cursor = {mCursorCol, mCursorRow}; - int newTotalRows = (mScreen == mAltBuffer) ? mRows : mMainBuffer.mTotalRows; - mScreen.resize(mColumns, mRows, newTotalRows, cursor, getStyle(), isAlternateBufferActive()); - mCursorCol = cursor[0]; - mCursorRow = cursor[1]; - } - - public int getCursorRow() { - return mCursorRow; - } - - public int getCursorCol() { - return mCursorCol; - } - - /** - * {@link #CURSOR_STYLE_BAR}, {@link #CURSOR_STYLE_BLOCK} or {@link #CURSOR_STYLE_UNDERLINE} - */ - public int getCursorStyle() { - return mCursorStyle; - } - - public boolean isReverseVideo() { - return isDecsetInternalBitSet(DECSET_BIT_REVERSE_VIDEO); - } - - public boolean isShowingCursor() { - return isDecsetInternalBitSet(DECSET_BIT_SHOWING_CURSOR); - } - - public boolean isKeypadApplicationMode() { - return isDecsetInternalBitSet(DECSET_BIT_APPLICATION_KEYPAD); - } - - public boolean isCursorKeysApplicationMode() { - return isDecsetInternalBitSet(DECSET_BIT_APPLICATION_CURSOR_KEYS); - } - - /** - * If mouse events are being sent as escape codes to the terminal. - */ - public boolean isMouseTrackingActive() { - return isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE) || isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT); - } - - private void setDefaultTabStops() { - for (int i = 0; i < mColumns; i++) - mTabStop[i] = (i & 7) == 0 && i != 0; - } - - /** - * Accept bytes (typically from the pseudo-teletype) and process them. - * - * @param buffer a byte array containing the bytes to be processed - * @param length the number of bytes in the array to process - */ - public void append(byte[] buffer, int length) { - for (int i = 0; i < length; i++) - processByte(buffer[i]); - } - - private void processByte(byte byteToProcess) { - if (mUtf8ToFollow > 0) { - if ((byteToProcess & 0b11000000) == 0b10000000) { - // 10xxxxxx, a continuation byte. - mUtf8InputBuffer[mUtf8Index++] = byteToProcess; - if (--mUtf8ToFollow == 0) { - byte firstByteMask = (byte) (mUtf8Index == 2 ? 0b00011111 : (mUtf8Index == 3 ? 0b00001111 : 0b00000111)); - int codePoint = (mUtf8InputBuffer[0] & firstByteMask); - for (int i = 1; i < mUtf8Index; i++) - codePoint = ((codePoint << 6) | (mUtf8InputBuffer[i] & 0b00111111)); - if (((codePoint <= 0b1111111) && mUtf8Index > 1) || (codePoint < 0b11111111111 && mUtf8Index > 2) - || (codePoint < 0b1111111111111111 && mUtf8Index > 3)) { - // Overlong encoding. - codePoint = UNICODE_REPLACEMENT_CHAR; - } - - mUtf8Index = mUtf8ToFollow = 0; - - if (codePoint >= 0x80 && codePoint <= 0x9F) { - // Sequence decoded to a C1 control character which is the same as escape followed by - // ((code & 0x7F) + 0x40). - // processCodePoint(/* escape (hexadecimal=0x1B, octal=033): */27); - // processCodePoint((codePoint & 0x7F) + 0x40); - - // Sequence decoded to a C1 control character which we ignore. They are - // not used nowadays and increases the risk of messing up the terminal state - // on binary input. XTerm does not allow them in utf-8: - // "It is not possible to use a C1 control obtained from decoding the - // UTF-8 text" - http://invisible-island.net/xterm/ctlseqs/ctlseqs.htm - } else { - switch (Character.getType(codePoint)) { - case Character.UNASSIGNED: - case Character.SURROGATE: - codePoint = UNICODE_REPLACEMENT_CHAR; - } - processCodePoint(codePoint); - } - } - } else { - // Not a UTF-8 continuation byte so replace the entire sequence up to now with the replacement char: - mUtf8Index = mUtf8ToFollow = 0; - emitCodePoint(UNICODE_REPLACEMENT_CHAR); - // The Unicode Standard Version 6.2 – Core Specification - // (http://www.unicode.org/versions/Unicode6.2.0/ch03.pdf): - // "If the converter encounters an ill-formed UTF-8 code unit sequence which starts with a valid first - // byte, but which does not continue with valid successor bytes (see Table 3-7), it must not consume the - // successor bytes as part of the ill-formed subsequence - // whenever those successor bytes themselves constitute part of a well-formed UTF-8 code unit - // subsequence." - processByte(byteToProcess); - } - } else { - if ((byteToProcess & 0b10000000) == 0) { // The leading bit is not set so it is a 7-bit ASCII character. - processCodePoint(byteToProcess); - return; - } else if ((byteToProcess & 0b11100000) == 0b11000000) { // 110xxxxx, a two-byte sequence. - mUtf8ToFollow = 1; - } else if ((byteToProcess & 0b11110000) == 0b11100000) { // 1110xxxx, a three-byte sequence. - mUtf8ToFollow = 2; - } else if ((byteToProcess & 0b11111000) == 0b11110000) { // 11110xxx, a four-byte sequence. - mUtf8ToFollow = 3; - } else { - // Not a valid UTF-8 sequence start, signal invalid data: - processCodePoint(UNICODE_REPLACEMENT_CHAR); - return; - } - mUtf8InputBuffer[mUtf8Index++] = byteToProcess; - } - } - - public void processCodePoint(int b) { - switch (b) { - case 0: // Null character (NUL, ^@). Do nothing. - break; - case 7: // Bell (BEL, ^G, \a). If in an OSC sequence, BEL may terminate a string; otherwise signal bell. - if (mEscapeState == ESC_OSC) - doOsc(b); - else - mSession.onBell(); - break; - case 8: // Backspace (BS, ^H). - if (mLeftMargin == mCursorCol) { - // Jump to previous line if it was auto-wrapped. - int previousRow = mCursorRow - 1; - if (previousRow >= 0 && mScreen.getLineWrap(previousRow)) { - mScreen.clearLineWrap(previousRow); - setCursorRowCol(previousRow, mRightMargin - 1); - } - } else { - setCursorCol(mCursorCol - 1); - } - break; - case 9: // Horizontal menu_main (HT, \t) - move to next menu_main stop, but not past edge of screen - // XXX: Should perhaps use color if writing to new cells. Try with - // printf "\033[41m\tXX\033[0m\n" - // The OSX Terminal.app colors the spaces from the menu_main red, but xterm does not. - // Note that Terminal.app only colors on new cells, in e.g. - // printf "\033[41m\t\r\033[42m\tXX\033[0m\n" - // the first cells are created with a red backgroundColor, but when tabbing over - // them again with a green backgroundColor they are not overwritten. - mCursorCol = nextTabStop(1); - break; - case 10: // Line feed (LF, \n). - case 11: // Vertical menu_main (VT, \v). - case 12: // Form feed (FF, \f). - doLinefeed(); - break; - case 13: // Carriage return (CR, \r). - setCursorCol(mLeftMargin); - break; - case 14: // Shift Out (Ctrl-N, SO) → Switch to Alternate Character Set. This invokes the G1 character set. - mUseLineDrawingUsesG0 = false; - break; - case 15: // Shift In (Ctrl-O, SI) → Switch to Standard Character Set. This invokes the G0 character set. - mUseLineDrawingUsesG0 = true; - break; - case 24: // CAN. - case 26: // SUB. - if (mEscapeState != ESC_NONE) { - // FIXME: What is this?? - mEscapeState = ESC_NONE; - emitCodePoint(127); - } - break; - case 27: // ESC - // Starts an escape sequence unless we're parsing a string - if (mEscapeState == ESC_P) { - // XXX: Ignore escape when reading device control sequence, since it may be part of string terminator. - return; - } else if (mEscapeState != ESC_OSC) { - startEscapeSequence(); - } else { - doOsc(b); - } - break; - default: - mContinueSequence = false; - switch (mEscapeState) { - case ESC_NONE: - if (b >= 32) emitCodePoint(b); - break; - case ESC: - doEsc(b); - break; - case ESC_POUND: - doEscPound(b); - break; - case ESC_SELECT_LEFT_PAREN: // Designate G0 Character Set (ISO 2022, VT100). - mUseLineDrawingG0 = (b == '0'); - break; - case ESC_SELECT_RIGHT_PAREN: // Designate G1 Character Set (ISO 2022, VT100). - mUseLineDrawingG1 = (b == '0'); - break; - case ESC_CSI: - doCsi(b); - break; - case ESC_CSI_EXCLAMATION: - if (b == 'p') { // Soft terminal reset (DECSTR, http://vt100.net/docs/vt510-rm/DECSTR). - reset(); - } else { - unknownSequence(b); - } - break; - case ESC_CSI_QUESTIONMARK: - doCsiQuestionMark(b); - break; - case ESC_CSI_BIGGERTHAN: - doCsiBiggerThan(b); - break; - case ESC_CSI_DOLLAR: - boolean originMode = isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE); - int effectiveTopMargin = originMode ? mTopMargin : 0; - int effectiveBottomMargin = originMode ? mBottomMargin : mRows; - int effectiveLeftMargin = originMode ? mLeftMargin : 0; - int effectiveRightMargin = originMode ? mRightMargin : mColumns; - switch (b) { - case 'v': // ${CSI}${SRC_TOP}${SRC_LEFT}${SRC_BOTTOM}${SRC_RIGHT}${SRC_PAGE}${DST_TOP}${DST_LEFT}${DST_PAGE}$v" - // Copy rectangular area (DECCRA - http://vt100.net/docs/vt510-rm/DECCRA): - // "If Pbs is greater than Pts, or Pls is greater than Prs, the terminal ignores DECCRA. - // The coordinates of the rectangular area are affected by the setting of origin mode (DECOM). - // DECCRA is not affected by the page margins. - // The copied text takes on the line attributes of the destination area. - // If the value of Pt, Pl, Pb, or Pr exceeds the width or height of the active page, then the value - // is treated as the width or height of that page. - // If the destination area is partially off the page, then DECCRA clips the off-page data. - // DECCRA does not change the active cursorColor position." - int topSource = Math.min(getArg(0, 1, true) - 1 + effectiveTopMargin, mRows); - int leftSource = Math.min(getArg(1, 1, true) - 1 + effectiveLeftMargin, mColumns); - // Inclusive, so do not subtract one: - int bottomSource = Math.min(Math.max(getArg(2, mRows, true) + effectiveTopMargin, topSource), mRows); - int rightSource = Math.min(Math.max(getArg(3, mColumns, true) + effectiveLeftMargin, leftSource), mColumns); - // int sourcePage = getArg(4, 1, true); - int destionationTop = Math.min(getArg(5, 1, true) - 1 + effectiveTopMargin, mRows); - int destinationLeft = Math.min(getArg(6, 1, true) - 1 + effectiveLeftMargin, mColumns); - // int destinationPage = getArg(7, 1, true); - int heightToCopy = Math.min(mRows - destionationTop, bottomSource - topSource); - int widthToCopy = Math.min(mColumns - destinationLeft, rightSource - leftSource); - mScreen.blockCopy(leftSource, topSource, widthToCopy, heightToCopy, destinationLeft, destionationTop); - break; - case '{': // ${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${" - // Selective erase rectangular area (DECSERA - http://www.vt100.net/docs/vt510-rm/DECSERA). - case 'x': // ${CSI}${CHAR};${TOP}${LEFT}${BOTTOM}${RIGHT}$x" - // Fill rectangular area (DECFRA - http://www.vt100.net/docs/vt510-rm/DECFRA). - case 'z': // ${CSI}$${TOP}${LEFT}${BOTTOM}${RIGHT}$z" - // Erase rectangular area (DECERA - http://www.vt100.net/docs/vt510-rm/DECERA). - boolean erase = b != 'x'; - boolean selective = b == '{'; - // Only DECSERA keeps visual attributes, DECERA does not: - boolean keepVisualAttributes = erase && selective; - int argIndex = 0; - int fillChar = erase ? ' ' : getArg(argIndex++, -1, true); - // "Pch can be any value from 32 to 126 or from 160 to 255. If Pch is not in this range, then the - // terminal ignores the DECFRA command": - if ((fillChar >= 32 && fillChar <= 126) || (fillChar >= 160 && fillChar <= 255)) { - // "If the value of Pt, Pl, Pb, or Pr exceeds the width or height of the active page, the value - // is treated as the width or height of that page." - int top = Math.min(getArg(argIndex++, 1, true) + effectiveTopMargin, effectiveBottomMargin + 1); - int left = Math.min(getArg(argIndex++, 1, true) + effectiveLeftMargin, effectiveRightMargin + 1); - int bottom = Math.min(getArg(argIndex++, mRows, true) + effectiveTopMargin, effectiveBottomMargin); - int right = Math.min(getArg(argIndex, mColumns, true) + effectiveLeftMargin, effectiveRightMargin); - long style = getStyle(); - for (int row = top - 1; row < bottom; row++) - for (int col = left - 1; col < right; col++) - if (!selective || (TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0) - mScreen.setChar(col, row, fillChar, keepVisualAttributes ? mScreen.getStyleAt(row, col) : style); - } - break; - case 'r': // "${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${ATTRIBUTES}$r" - // Change attributes in rectangular area (DECCARA - http://vt100.net/docs/vt510-rm/DECCARA). - case 't': // "${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${ATTRIBUTES}$t" - // Reverse attributes in rectangular area (DECRARA - http://www.vt100.net/docs/vt510-rm/DECRARA). - boolean reverse = b == 't'; - // FIXME: "coordinates of the rectangular area are affected by the setting of origin mode (DECOM)". - int top = Math.min(getArg(0, 1, true) - 1, effectiveBottomMargin) + effectiveTopMargin; - int left = Math.min(getArg(1, 1, true) - 1, effectiveRightMargin) + effectiveLeftMargin; - int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin; - int right = Math.min(getArg(3, mColumns, true) + 1, effectiveRightMargin - 1) + effectiveLeftMargin; - if (mArgIndex >= 4) { - if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; - for (int i = 4; i <= mArgIndex; i++) { - int bits = 0; - boolean setOrClear = true; // True if setting, false if clearing. - switch (getArg(i, 0, false)) { - case 0: // Attributes off (no bold, no underline, no blink, positive image). - bits = (TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE | TextStyle.CHARACTER_ATTRIBUTE_BLINK - | TextStyle.CHARACTER_ATTRIBUTE_INVERSE); - if (!reverse) setOrClear = false; - break; - case 1: // Bold. - bits = TextStyle.CHARACTER_ATTRIBUTE_BOLD; - break; - case 4: // Underline. - bits = TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; - break; - case 5: // Blink. - bits = TextStyle.CHARACTER_ATTRIBUTE_BLINK; - break; - case 7: // Negative image. - bits = TextStyle.CHARACTER_ATTRIBUTE_INVERSE; - break; - case 22: // No bold. - bits = TextStyle.CHARACTER_ATTRIBUTE_BOLD; - setOrClear = false; - break; - case 24: // No underline. - bits = TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; - setOrClear = false; - break; - case 25: // No blink. - bits = TextStyle.CHARACTER_ATTRIBUTE_BLINK; - setOrClear = false; - break; - case 27: // Positive image. - bits = TextStyle.CHARACTER_ATTRIBUTE_INVERSE; - setOrClear = false; - break; - } - if (reverse && !setOrClear) { - // Reverse attributes in rectangular area ignores non-(1,4,5,7) bits. - } else { - mScreen.setOrClearEffect(bits, setOrClear, reverse, isDecsetInternalBitSet(DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE), - effectiveLeftMargin, effectiveRightMargin, top, left, bottom, right); - } - } - } else { - // Do nothing. - } - break; - default: - unknownSequence(b); - } - break; - case ESC_CSI_DOUBLE_QUOTE: - if (b == 'q') { - // http://www.vt100.net/docs/vt510-rm/DECSCA - int arg = getArg0(0); - if (arg == 0 || arg == 2) { - // DECSED and DECSEL can erase characters. - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_PROTECTED; - } else if (arg == 1) { - // DECSED and DECSEL cannot erase characters. - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_PROTECTED; - } else { - unknownSequence(b); - } - } else { - unknownSequence(b); - } - break; - case ESC_CSI_SINGLE_QUOTE: - if (b == '}') { // Insert Ps Column(s) (default = 1) (DECIC), VT420 and up. - int columnsAfterCursor = mRightMargin - mCursorCol; - int columnsToInsert = Math.min(getArg0(1), columnsAfterCursor); - int columnsToMove = columnsAfterCursor - columnsToInsert; - mScreen.blockCopy(mCursorCol, 0, columnsToMove, mRows, mCursorCol + columnsToInsert, 0); - blockClear(mCursorCol, 0, columnsToInsert, mRows); - } else if (b == '~') { // Delete Ps Column(s) (default = 1) (DECDC), VT420 and up. - int columnsAfterCursor = mRightMargin - mCursorCol; - int columnsToDelete = Math.min(getArg0(1), columnsAfterCursor); - int columnsToMove = columnsAfterCursor - columnsToDelete; - mScreen.blockCopy(mCursorCol + columnsToDelete, 0, columnsToMove, mRows, mCursorCol, 0); - blockClear(mCursorRow + columnsToMove, 0, columnsToDelete, mRows); - } else { - unknownSequence(b); - } - break; - case ESC_PERCENT: - break; - case ESC_OSC: - doOsc(b); - break; - case ESC_OSC_ESC: - doOscEsc(b); - break; - case ESC_P: - doDeviceControl(b); - break; - case ESC_CSI_QUESTIONMARK_ARG_DOLLAR: - if (b == 'p') { - // Request DEC private mode (DECRQM). - int mode = getArg0(0); - int value; - if (mode == 47 || mode == 1047 || mode == 1049) { - // This state is carried by mScreen pointer. - value = (mScreen == mAltBuffer) ? 1 : 2; - } else { - int internalBit = mapDecSetBitToInternalBit(mode); - if (internalBit == -1) { - value = isDecsetInternalBitSet(internalBit) ? 1 : 2; // 1=set, 2=reset. - } else { - Log.e(EmulatorDebug.LOG_TAG, "Got DECRQM for unrecognized private DEC mode=" + mode); - value = 0; // 0=not recognized, 3=permanently set, 4=permanently reset - } - } - mSession.write(String.format(Locale.US, "\033[?%d;%d$y", mode, value)); - } else { - unknownSequence(b); - } - break; - case ESC_CSI_ARGS_SPACE: - int arg = getArg0(0); - switch (b) { - case 'q': // "${CSI}${STYLE} q" - set cursorColor style (http://www.vt100.net/docs/vt510-rm/DECSCUSR). - switch (arg) { - case 0: // Blinking block. - case 1: // Blinking block. - case 2: // Steady block. - mCursorStyle = CURSOR_STYLE_BLOCK; - break; - case 3: // Blinking underline. - case 4: // Steady underline. - mCursorStyle = CURSOR_STYLE_UNDERLINE; - break; - case 5: // Blinking bar (xterm addition). - case 6: // Steady bar (xterm addition). - mCursorStyle = CURSOR_STYLE_BAR; - break; - } - break; - case 't': - case 'u': - // Set margin-bell volume - ignore. - break; - default: - unknownSequence(b); - } - break; - case ESC_CSI_ARGS_ASTERIX: - int attributeChangeExtent = getArg0(0); - if (b == 'x' && (attributeChangeExtent >= 0 && attributeChangeExtent <= 2)) { - // Select attribute change extent (DECSACE - http://www.vt100.net/docs/vt510-rm/DECSACE). - setDecsetinternalBit(DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE, attributeChangeExtent == 2); - } else { - unknownSequence(b); - } - break; - default: - unknownSequence(b); - break; - } - if (!mContinueSequence) mEscapeState = ESC_NONE; - break; - } - } - - /** - * When in {@link #ESC_P} ("device control") sequence. - */ - private void doDeviceControl(int b) { - switch (b) { - case (byte) '\\': // End of ESC \ string Terminator - { - String dcs = mOSCOrDeviceControlArgs.toString(); - // DCS $ q P t ST. Request Status String (DECRQSS) - if (dcs.startsWith("$q")) { - if (dcs.equals("$q\"p")) { - // DECSCL, conformance level, http://www.vt100.net/docs/vt510-rm/DECSCL: - String csiString = "64;1\"p"; - mSession.write("\033P1$r" + csiString + "\033\\"); - } else { - finishSequenceAndLogError("Unrecognized DECRQSS string: '" + dcs + "'"); - } - } else if (dcs.startsWith("+q")) { - // Request Termcap/Terminfo String. The string following the "q" is a list of names encoded in - // hexadecimal (2 digits per character) separated by ; which correspond to termcap or terminfo key - // names. - // Two special features are also recognized, which are not key names: Co for termcap colors (or colors - // for terminfo colors), and TN for termcap name (or name for terminfo name). - // xterm responds with DCS 1 + r P t ST for valid requests, adding to P t an = , and the value of the - // corresponding string that xterm would send, or DCS 0 + r P t ST for invalid requests. The strings are - // encoded in hexadecimal (2 digits per character). - // Example: - // :kr=\EOC: ks=\E[?1h\E=: ku=\EOA: le=^H:mb=\E[5m:md=\E[1m:\ - // where - // kd=down-arrow key - // kl=left-arrow key - // kr=right-arrow key - // ku=up-arrow key - // #2=key_shome, "shifted home" - // #4=key_sleft, "shift arrow left" - // %i=key_sright, "shift arrow right" - // *7=key_send, "shifted end" - // k1=F1 function key - - // Example: Request for ku is "ESC P + q 6 b 7 5 ESC \", where 6b7d=ku in hexadecimal. - // Xterm response in normal cursorColor mode: - // "<27> P 1 + r 6 b 7 5 = 1 B 5 B 4 1" where 0x1B 0x5B 0x41 = 27 91 65 = ESC [ A - // Xterm response in application cursorColor mode: - // "<27> P 1 + r 6 b 7 5 = 1 B 5 B 4 1" where 0x1B 0x4F 0x41 = 27 91 65 = ESC 0 A - - // #4 is "shift arrow left": - // *** Device Control (DCS) for '#4'- 'ESC P + q 23 34 ESC \' - // Response: <27> P 1 + r 2 3 3 4 = 1 B 5 B 3 1 3 B 3 2 4 4 <27> \ - // where 0x1B 0x5B 0x31 0x3B 0x32 0x44 = ESC [ 1 ; 2 D - // which we find in: TermKeyListener.java: KEY_MAP.put(KEYMOD_SHIFT | KEYCODE_DPAD_LEFT, "\033[1;2D"); - - // See http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V40G_HTML/MAN/MAN4/0178____.HTM for what to - // respond, as well as http://www.freebsd.org/cgi/man.cgi?query=termcap&sektion=5#CAPABILITIES for - // the meaning of e.g. "ku", "kd", "kr", "kl" - - for (String part : dcs.substring(2).split(";")) { - if (part.length() % 2 == 0) { - StringBuilder transBuffer = new StringBuilder(); - for (int i = 0; i < part.length(); i += 2) { - char c = (char) Long.decode("0x" + part.charAt(i) + "" + part.charAt(i + 1)).longValue(); - transBuffer.append(c); - } - String trans = transBuffer.toString(); - String responseValue; - switch (trans) { - case "Co": - case "colors": - responseValue = "256"; // Number of colors. - break; - case "TN": - case "name": - responseValue = "xterm"; - break; - default: - responseValue = KeyHandler.getCodeFromTermcap(trans, isDecsetInternalBitSet(DECSET_BIT_APPLICATION_CURSOR_KEYS), - isDecsetInternalBitSet(DECSET_BIT_APPLICATION_KEYPAD)); - break; - } - if (responseValue == null) { - switch (trans) { - case "%1": // Help key - ignore - case "&8": // Undo key - ignore. - break; - default: - Log.w(EmulatorDebug.LOG_TAG, "Unhandled termcap/terminfo name: '" + trans + "'"); - } - // Respond with invalid request: - mSession.write("\033P0+r" + part + "\033\\"); - } else { - StringBuilder hexEncoded = new StringBuilder(); - for (int j = 0; j < responseValue.length(); j++) { - hexEncoded.append(String.format("%02X", (int) responseValue.charAt(j))); - } - mSession.write("\033P1+r" + part + "=" + hexEncoded + "\033\\"); - } - } else { - Log.e(EmulatorDebug.LOG_TAG, "Invalid device termcap/terminfo name of odd length: " + part); - } - } - } else { - if (LOG_ESCAPE_SEQUENCES) - Log.e(EmulatorDebug.LOG_TAG, "Unrecognized device control string: " + dcs); - } - finishSequence(); - } - break; - default: - if (mOSCOrDeviceControlArgs.length() > MAX_OSC_STRING_LENGTH) { - // Too long. - mOSCOrDeviceControlArgs.setLength(0); - finishSequence(); - } else { - mOSCOrDeviceControlArgs.appendCodePoint(b); - continueSequence(mEscapeState); - } - } - } - - private int nextTabStop(int numTabs) { - for (int i = mCursorCol + 1; i < mColumns; i++) - if (mTabStop[i] && --numTabs == 0) return Math.min(i, mRightMargin); - return mRightMargin - 1; - } - - /** - * Process byte while in the {@link #ESC_CSI_QUESTIONMARK} escape state. - */ - private void doCsiQuestionMark(int b) { - switch (b) { - case 'J': // Selective erase in display (DECSED) - http://www.vt100.net/docs/vt510-rm/DECSED. - case 'K': // Selective erase in line (DECSEL) - http://vt100.net/docs/vt510-rm/DECSEL. - mAboutToAutoWrap = false; - int fillChar = ' '; - int startCol = -1; - int startRow = -1; - int endCol = -1; - int endRow = -1; - boolean justRow = (b == 'K'); - switch (getArg0(0)) { - case 0: // Erase from the active position to the end, inclusive (default). - startCol = mCursorCol; - startRow = mCursorRow; - endCol = mColumns; - endRow = justRow ? (mCursorRow + 1) : mRows; - break; - case 1: // Erase from start to the active position, inclusive. - startCol = 0; - startRow = justRow ? mCursorRow : 0; - endCol = mCursorCol + 1; - endRow = mCursorRow + 1; - break; - case 2: // Erase all of the display/line. - startCol = 0; - startRow = justRow ? mCursorRow : 0; - endCol = mColumns; - endRow = justRow ? (mCursorRow + 1) : mRows; - break; - default: - unknownSequence(b); - break; - } - long style = getStyle(); - for (int row = startRow; row < endRow; row++) { - for (int col = startCol; col < endCol; col++) { - if ((TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0) - mScreen.setChar(col, row, fillChar, style); - } - } - break; - case 'h': - case 'l': - if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; - for (int i = 0; i <= mArgIndex; i++) - doDecSetOrReset(b == 'h', mArgs[i]); - break; - case 'n': // Device Status Report (DSR, DEC-specific). - switch (getArg0(-1)) { - case 6: - // Extended Cursor Position (DECXCPR - http://www.vt100.net/docs/vt510-rm/DECXCPR). Page=1. - mSession.write(String.format(Locale.US, "\033[?%d;%d;1R", mCursorRow + 1, mCursorCol + 1)); - break; - default: - finishSequence(); - return; - } - break; - case 'r': - case 's': - if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; - for (int i = 0; i <= mArgIndex; i++) { - int externalBit = mArgs[i]; - int internalBit = mapDecSetBitToInternalBit(externalBit); - if (internalBit == -1) { - Log.w(EmulatorDebug.LOG_TAG, "Ignoring request to save/recall decset bit=" + externalBit); - } else { - if (b == 's') { - mSavedDecSetFlags |= internalBit; - } else { - doDecSetOrReset((mSavedDecSetFlags & internalBit) != 0, externalBit); - } - } - } - break; - case '$': - continueSequence(ESC_CSI_QUESTIONMARK_ARG_DOLLAR); - return; - default: - parseArg(b); - } - } - - public void doDecSetOrReset(boolean setting, int externalBit) { - int internalBit = mapDecSetBitToInternalBit(externalBit); - if (internalBit != -1) { - setDecsetinternalBit(internalBit, setting); - } - switch (externalBit) { - case 1: // Application Cursor Keys (DECCKM). - break; - case 3: // Set: 132 column mode (. Reset: 80 column mode. ANSI name: DECCOLM. - // We don't actually set/reset 132 cols, but we do want the side effects - // (FIXME: Should only do this if the 95 DECSET bit (DECNCSM) is set, and if changing value?): - // Sets the left, right, top and bottom scrolling margins to their default positions, which is important for - // the "reset" utility to really reset the terminal: - mLeftMargin = mTopMargin = 0; - mBottomMargin = mRows; - mRightMargin = mColumns; - // "DECCOLM resets vertical split screen mode (DECLRMM) to unavailable": - setDecsetinternalBit(DECSET_BIT_LEFTRIGHT_MARGIN_MODE, false); - // "Erases all data in page memory": - blockClear(0, 0, mColumns, mRows); - setCursorRowCol(0, 0); - break; - case 4: // DECSCLM-Scrolling Mode. Ignore. - break; - case 5: // Reverse video. No action. - break; - case 6: // Set: Origin Mode. Reset: Normal Cursor Mode. Ansi name: DECOM. - if (setting) setCursorPosition(0, 0); - break; - case 7: // Wrap-around bit, not specific action. - case 8: // Auto-repeat Keys (DECARM). Do not implement. - case 9: // X10 mouse reporting - outdated. Do not implement. - case 12: // Control cursorColor blinking - ignore. - case 25: // Hide/show cursorColor - no action needed, renderer will check with isShowingCursor(). - case 40: // Allow 80 => 132 Mode, ignore. - case 45: // TODO: Reverse wrap-around. Implement??? - case 66: // Application keypad (DECNKM). - break; - case 69: // Left and right margin mode (DECLRMM). - if (!setting) { - mLeftMargin = 0; - mRightMargin = mColumns; - } - break; - case 1000: - case 1001: - case 1002: - case 1003: - case 1004: - case 1005: // UTF-8 mouse mode, ignore. - case 1006: // SGR Mouse Mode - case 1015: - case 1034: // Interpret "meta" key, sets eighth bit. - break; - case 1048: // Set: Save cursorColor as in DECSC. Reset: Restore cursorColor as in DECRC. - if (setting) - saveCursor(); - else - restoreCursor(); - break; - case 47: - case 1047: - case 1049: { - // Set: Save cursorColor as in DECSC and use Alternate Screen Buffer, clearing it first. - // Reset: Use Normal Screen Buffer and restore cursorColor as in DECRC. - TerminalBuffer newScreen = setting ? mAltBuffer : mMainBuffer; - if (newScreen != mScreen) { - boolean resized = !(newScreen.mColumns == mColumns && newScreen.mScreenRows == mRows); - if (setting) saveCursor(); - mScreen = newScreen; - if (!setting) { - int col = mSavedStateMain.mSavedCursorCol; - int row = mSavedStateMain.mSavedCursorRow; - restoreCursor(); - if (resized) { - // Restore cursorColor position _not_ clipped to current screen (let resizeScreen() handle that): - mCursorCol = col; - mCursorRow = row; - } - } - // Check if buffer size needs to be updated: - if (resized) resizeScreen(); - // Clear new screen if alt buffer: - if (newScreen == mAltBuffer) - newScreen.blockSet(0, 0, mColumns, mRows, ' ', getStyle()); - } - break; - } - case 2004: - // Bracketed paste mode - setting bit is enough. - break; - default: - unknownParameter(externalBit); - break; - } - } - - private void doCsiBiggerThan(int b) { - switch (b) { - case 'c': // "${CSI}>c" or "${CSI}>c". Secondary Device Attributes (DA2). - // Originally this was used for the terminal to respond with "identification code, firmware version level, - // and hardware options" (http://vt100.net/docs/vt510-rm/DA2), with the first "41" meaning the VT420 - // terminal type. This is not used anymore, but the second version level field has been changed by xterm - // to mean it's release number ("patch numbers" listed at http://invisible-island.net/xterm/xterm.log.html), - // and some applications use it as a feature check: - // * tmux used to have a "xterm won't reach version 500 for a while so set that as the upper limit" check, - // and then check "xterm_version > 270" if rectangular area operations such as DECCRA could be used. - // * vim checks xterm version number >140 for "Request termcap/terminfo string" functionality >276 for SGR - // mouse report. - // The third number is a keyboard identifier not used nowadays. - mSession.write("\033[>41;320;0c"); - break; - case 'm': - // https://bugs.launchpad.net/gnome-terminal/+bug/96676/comments/25 - // Depending on the first number parameter, this can set one of the xterm resources - // modifyKeyboard, modifyCursorKeys, modifyFunctionKeys and modifyOtherKeys. - // http://invisible-island.net/xterm/manpage/xterm.html#RESOURCES - - // * modifyKeyboard (parameter=1): - // Normally xterm makes a special case regarding modifiers (shift, control, etc.) to handle special keyboard - // layouts (legacy and vt220). This is done to provide compatible keyboards for DEC VT220 and related - // terminals that implement user-defined keys (UDK). - // The bits of the resource value selectively enable modification of the given category when these keyboards - // are selected. The default is "0": - // (0) The legacy/vt220 keyboards interpret only the Control-modifier when constructing numbered - // function-keys. Other special keys are not modified. - // (1) allows modification of the numeric keypad - // (2) allows modification of the editing keypad - // (4) allows modification of function-keys, overrides use of Shift-modifier for UDK. - // (8) allows modification of other special keys - - // * modifyCursorKeys (parameter=2): - // Tells how to handle the special case where Control-, Shift-, Alt- or Meta-modifiers are used to add a - // parameter to the escape sequence returned by a cursorColor-key. The default is "2". - // - Set it to -1 to disable it. - // - Set it to 0 to use the old/obsolete behavior. - // - Set it to 1 to prefix modified sequences with CSI. - // - Set it to 2 to force the modifier to be the second parameter if it would otherwise be the first. - // - Set it to 3 to mark the sequence with a ">" to hint that it is private. - - // * modifyFunctionKeys (parameter=3): - // Tells how to handle the special case where Control-, Shift-, Alt- or Meta-modifiers are used to add a - // parameter to the escape sequence returned by a (numbered) function- - // key. The default is "2". The resource values are similar to modifyCursorKeys: - // Set it to -1 to permit the user to use shift- and control-modifiers to construct function-key strings - // using the normal encoding scheme. - // - Set it to 0 to use the old/obsolete behavior. - // - Set it to 1 to prefix modified sequences with CSI. - // - Set it to 2 to force the modifier to be the second parameter if it would otherwise be the first. - // - Set it to 3 to mark the sequence with a ">" to hint that it is private. - // If modifyFunctionKeys is zero, xterm uses Control- and Shift-modifiers to allow the user to construct - // numbered function-keys beyond the set provided by the keyboard: - // (Control) adds the value given by the ctrlFKeys resource. - // (Shift) adds twice the value given by the ctrlFKeys resource. - // (Control/Shift) adds three times the value given by the ctrlFKeys resource. - // - // As a special case, legacy (when oldFunctionKeys is true) or vt220 (when sunKeyboard is true) - // keyboards interpret only the Control-modifier when constructing numbered function-keys. - // This is done to provide compatible keyboards for DEC VT220 and related terminals that - // implement user-defined keys (UDK). - - // * modifyOtherKeys (parameter=4): - // Like modifyCursorKeys, tells xterm to construct an escape sequence for other keys (such as "2") when - // modified by Control-, Alt- or Meta-modifiers. This feature does not apply to function keys and - // well-defined keys such as ESC or the control keys. The default is "0". - // (0) disables this feature. - // (1) enables this feature for keys except for those with well-known behavior, e.g., Tab, Backarrow and - // some special control character cases, e.g., Control-Space to make a NUL. - // (2) enables this feature for keys including the exceptions listed. - Log.e(EmulatorDebug.LOG_TAG, "(ignored) CSI > MODIFY RESOURCE: " + getArg0(-1) + " to " + getArg1(-1)); - break; - default: - parseArg(b); - break; - } - } - - private void startEscapeSequence() { - mEscapeState = ESC; - mArgIndex = 0; - Arrays.fill(mArgs, -1); - } - - private void doLinefeed() { - boolean belowScrollingRegion = mCursorRow >= mBottomMargin; - int newCursorRow = mCursorRow + 1; - if (belowScrollingRegion) { - // Move down (but not scroll) as long as we are above the last row. - if (mCursorRow != mRows - 1) { - setCursorRow(newCursorRow); - } - } else { - if (newCursorRow == mBottomMargin) { - scrollDownOneLine(); - newCursorRow = mBottomMargin - 1; - } - setCursorRow(newCursorRow); - } - } - - private void continueSequence(int state) { - mEscapeState = state; - mContinueSequence = true; - } - - private void doEscPound(int b) { - switch (b) { - case '8': // Esc # 8 - DEC screen alignment test - fill screen with E's. - mScreen.blockSet(0, 0, mColumns, mRows, 'E', getStyle()); - break; - default: - unknownSequence(b); - break; - } - } - - /** - * Encountering a character in the {@link #ESC} state. - */ - private void doEsc(int b) { - switch (b) { - case '#': - continueSequence(ESC_POUND); - break; - case '(': - continueSequence(ESC_SELECT_LEFT_PAREN); - break; - case ')': - continueSequence(ESC_SELECT_RIGHT_PAREN); - break; - case '6': // Back index (http://www.vt100.net/docs/vt510-rm/DECBI). Move left, insert blank column if start. - if (mCursorCol > mLeftMargin) { - mCursorCol--; - } else { - int rows = mBottomMargin - mTopMargin; - mScreen.blockCopy(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin - 1, rows, mLeftMargin + 1, mTopMargin); - mScreen.blockSet(mLeftMargin, mTopMargin, 1, rows, ' ', TextStyle.encode(mForeColor, mBackColor, 0)); - } - break; - case '7': // DECSC save cursorColor - http://www.vt100.net/docs/vt510-rm/DECSC - saveCursor(); - break; - case '8': // DECRC restore cursorColor - http://www.vt100.net/docs/vt510-rm/DECRC - restoreCursor(); - break; - case '9': // Forward Index (http://www.vt100.net/docs/vt510-rm/DECFI). Move right, insert blank column if end. - if (mCursorCol < mRightMargin - 1) { - mCursorCol++; - } else { - int rows = mBottomMargin - mTopMargin; - mScreen.blockCopy(mLeftMargin + 1, mTopMargin, mRightMargin - mLeftMargin - 1, rows, mLeftMargin, mTopMargin); - mScreen.blockSet(mRightMargin - 1, mTopMargin, 1, rows, ' ', TextStyle.encode(mForeColor, mBackColor, 0)); - } - break; - case 'c': // RIS - Reset to Initial State (http://vt100.net/docs/vt510-rm/RIS). - reset(); - blockClear(0, 0, mColumns, mRows); - setCursorPosition(0, 0); - break; - case 'D': // INDEX - doLinefeed(); - break; - case 'E': // Next line (http://www.vt100.net/docs/vt510-rm/NEL). - setCursorCol(isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE) ? mLeftMargin : 0); - doLinefeed(); - break; - case 'F': // Cursor to lower-left corner of screen - setCursorRowCol(0, mBottomMargin - 1); - break; - case 'H': // Tab set - mTabStop[mCursorCol] = true; - break; - case 'M': // "${ESC}M" - reverse index (RI). - // http://www.vt100.net/docs/vt100-ug/chapter3.html: "Move the active position to the same horizontal - // position on the preceding line. If the active position is at the top margin, a scroll down is performed". - if (mCursorRow <= mTopMargin) { - mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin - (mTopMargin + 1), 0, mTopMargin + 1); - blockClear(0, mTopMargin, mColumns); - } else { - mCursorRow--; - } - break; - case 'N': // SS2, ignore. - case '0': // SS3, ignore. - break; - case 'P': // Device control string - mOSCOrDeviceControlArgs.setLength(0); - continueSequence(ESC_P); - break; - case '[': - continueSequence(ESC_CSI); - break; - case '=': // DECKPAM - setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, true); - break; - case ']': // OSC - mOSCOrDeviceControlArgs.setLength(0); - continueSequence(ESC_OSC); - break; - case '>': // DECKPNM - setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, false); - break; - default: - unknownSequence(b); - break; - } - } - - /** - * DECSC save cursorColor - http://www.vt100.net/docs/vt510-rm/DECSC . See {@link #restoreCursor()}. - */ - private void saveCursor() { - SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt; - state.mSavedCursorRow = mCursorRow; - state.mSavedCursorCol = mCursorCol; - state.mSavedEffect = mEffect; - state.mSavedForeColor = mForeColor; - state.mSavedBackColor = mBackColor; - state.mSavedDecFlags = mCurrentDecSetFlags; - state.mUseLineDrawingG0 = mUseLineDrawingG0; - state.mUseLineDrawingG1 = mUseLineDrawingG1; - state.mUseLineDrawingUsesG0 = mUseLineDrawingUsesG0; - } - - /** - * DECRS restore cursorColor - http://www.vt100.net/docs/vt510-rm/DECRC. See {@link #saveCursor()}. - */ - private void restoreCursor() { - SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt; - setCursorRowCol(state.mSavedCursorRow, state.mSavedCursorCol); - mEffect = state.mSavedEffect; - mForeColor = state.mSavedForeColor; - mBackColor = state.mSavedBackColor; - int mask = (DECSET_BIT_AUTOWRAP | DECSET_BIT_ORIGIN_MODE); - mCurrentDecSetFlags = (mCurrentDecSetFlags & ~mask) | (state.mSavedDecFlags & mask); - mUseLineDrawingG0 = state.mUseLineDrawingG0; - mUseLineDrawingG1 = state.mUseLineDrawingG1; - mUseLineDrawingUsesG0 = state.mUseLineDrawingUsesG0; - } - - /** - * Following a CSI - Control Sequence Introducer, "\033[". {@link #ESC_CSI}. - */ - private void doCsi(int b) { - switch (b) { - case '!': - continueSequence(ESC_CSI_EXCLAMATION); - break; - case '"': - continueSequence(ESC_CSI_DOUBLE_QUOTE); - break; - case '\'': - continueSequence(ESC_CSI_SINGLE_QUOTE); - break; - case '$': - continueSequence(ESC_CSI_DOLLAR); - break; - case '*': - continueSequence(ESC_CSI_ARGS_ASTERIX); - break; - case '@': { - // "CSI{n}@" - Insert ${n} space characters (ICH) - http://www.vt100.net/docs/vt510-rm/ICH. - mAboutToAutoWrap = false; - int columnsAfterCursor = mColumns - mCursorCol; - int spacesToInsert = Math.min(getArg0(1), columnsAfterCursor); - int charsToMove = columnsAfterCursor - spacesToInsert; - mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1, mCursorCol + spacesToInsert, mCursorRow); - blockClear(mCursorCol, mCursorRow, spacesToInsert); - } - break; - case 'A': // "CSI${n}A" - Cursor up (CUU) ${n} rows. - setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1))); - break; - case 'B': // "CSI${n}B" - Cursor down (CUD) ${n} rows. - setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1))); - break; - case 'C': // "CSI${n}C" - Cursor forward (CUF). - case 'a': // "CSI${n}a" - Horizontal position relative (HPR). From ISO-6428/ECMA-48. - setCursorCol(Math.min(mRightMargin - 1, mCursorCol + getArg0(1))); - break; - case 'D': // "CSI${n}D" - Cursor backward (CUB) ${n} columns. - setCursorCol(Math.max(mLeftMargin, mCursorCol - getArg0(1))); - break; - case 'E': // "CSI{n}E - Cursor Next Line (CNL). From ISO-6428/ECMA-48. - setCursorPosition(0, mCursorRow + getArg0(1)); - break; - case 'F': // "CSI{n}F - Cursor Previous Line (CPL). From ISO-6428/ECMA-48. - setCursorPosition(0, mCursorRow - getArg0(1)); - break; - case 'G': // "CSI${n}G" - Cursor horizontal absolute (CHA) to column ${n}. - setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1); - break; - case 'H': // "${CSI}${ROW};${COLUMN}H" - Cursor position (CUP). - case 'f': // "${CSI}${ROW};${COLUMN}f" - Horizontal and Vertical Position (HVP). - setCursorPosition(getArg1(1) - 1, getArg0(1) - 1); - break; - case 'I': // Cursor Horizontal Forward Tabulation (CHT). Move the active position n tabs forward. - setCursorCol(nextTabStop(getArg0(1))); - break; - case 'J': // "${CSI}${0,1,2}J" - Erase in Display (ED) - // ED ignores the scrolling margins. - switch (getArg0(0)) { - case 0: // Erase from the active position to the end of the screen, inclusive (default). - blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); - blockClear(0, mCursorRow + 1, mColumns, mRows - (mCursorRow + 1)); - break; - case 1: // Erase from start of the screen to the active position, inclusive. - blockClear(0, 0, mColumns, mCursorRow); - blockClear(0, mCursorRow, mCursorCol + 1); - break; - case 2: // Erase all of the display - all lines are erased, changed to single-width, and the cursorColor does not - // move.. - blockClear(0, 0, mColumns, mRows); - break; - default: - unknownSequence(b); - return; - } - mAboutToAutoWrap = false; - break; - case 'K': // "CSI{n}K" - Erase in line (EL). - switch (getArg0(0)) { - case 0: // Erase from the cursorColor to the end of the line, inclusive (default) - blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); - break; - case 1: // Erase from the start of the screen to the cursorColor, inclusive. - blockClear(0, mCursorRow, mCursorCol + 1); - break; - case 2: // Erase all of the line. - blockClear(0, mCursorRow, mColumns); - break; - default: - unknownSequence(b); - return; - } - mAboutToAutoWrap = false; - break; - case 'L': // "${CSI}{N}L" - insert ${N} lines (IL). - { - int linesAfterCursor = mBottomMargin - mCursorRow; - int linesToInsert = Math.min(getArg0(1), linesAfterCursor); - int linesToMove = linesAfterCursor - linesToInsert; - mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0, mCursorRow + linesToInsert); - blockClear(0, mCursorRow, mColumns, linesToInsert); - } - break; - case 'M': // "${CSI}${N}M" - delete N lines (DL). - { - mAboutToAutoWrap = false; - int linesAfterCursor = mBottomMargin - mCursorRow; - int linesToDelete = Math.min(getArg0(1), linesAfterCursor); - int linesToMove = linesAfterCursor - linesToDelete; - mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns, linesToMove, 0, mCursorRow); - blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete); - } - break; - case 'P': // "${CSI}{N}P" - delete ${N} characters (DCH). - { - // http://www.vt100.net/docs/vt510-rm/DCH: "If ${N} is greater than the number of characters between the - // cursorColor and the right margin, then DCH only deletes the remaining characters. - // As characters are deleted, the remaining characters between the cursorColor and right margin move to the left. - // Character attributes move with the characters. The terminal adds blank spaces with no visual character - // attributes at the right margin. DCH has no effect outside the scrolling margins." - mAboutToAutoWrap = false; - int cellsAfterCursor = mColumns - mCursorCol; - int cellsToDelete = Math.min(getArg0(1), cellsAfterCursor); - int cellsToMove = cellsAfterCursor - cellsToDelete; - mScreen.blockCopy(mCursorCol + cellsToDelete, mCursorRow, cellsToMove, 1, mCursorCol, mCursorRow); - blockClear(mCursorCol + cellsToMove, mCursorRow, cellsToDelete); - } - break; - case 'S': { // "${CSI}${N}S" - scroll up ${N} lines (default = 1) (SU). - final int linesToScroll = getArg0(1); - for (int i = 0; i < linesToScroll; i++) - scrollDownOneLine(); - break; - } - case 'T': - if (mArgIndex == 0) { - // "${CSI}${N}T" - Scroll down N lines (default = 1) (SD). - // http://vt100.net/docs/vt510-rm/SD: "N is the number of lines to move the user window up in page - // memory. N new lines appear at the top of the display. N old lines disappear at the bottom of the - // display. You cannot pan past the top margin of the current page". - final int linesToScrollArg = getArg0(1); - final int linesBetweenTopAndBottomMargins = mBottomMargin - mTopMargin; - final int linesToScroll = Math.min(linesBetweenTopAndBottomMargins, linesToScrollArg); - mScreen.blockCopy(0, mTopMargin, mColumns, linesBetweenTopAndBottomMargins - linesToScroll, 0, mTopMargin + linesToScroll); - blockClear(0, mTopMargin, mColumns, linesToScroll); - } else { - // "${CSI}${func};${startx};${starty};${firstrow};${lastrow}T" - initiate highlight mouse tracking. - unimplementedSequence(b); - } - break; - case 'X': // "${CSI}${N}X" - Erase ${N:=1} character(s) (ECH). FIXME: Clears character attributes? - mAboutToAutoWrap = false; - mScreen.blockSet(mCursorCol, mCursorRow, Math.min(getArg0(1), mColumns - mCursorCol), 1, ' ', getStyle()); - break; - case 'Z': // Cursor Backward Tabulation (CBT). Move the active position n tabs backward. - int numberOfTabs = getArg0(1); - int newCol = mLeftMargin; - for (int i = mCursorCol - 1; i >= 0; i--) - if (mTabStop[i]) { - if (--numberOfTabs == 0) { - newCol = Math.max(i, mLeftMargin); - break; - } - } - mCursorCol = newCol; - break; - case '?': // Esc [ ? -- start of a private mode set - continueSequence(ESC_CSI_QUESTIONMARK); - break; - case '>': // "Esc [ >" -- - continueSequence(ESC_CSI_BIGGERTHAN); - break; - case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA). - setCursorColRespectingOriginMode(getArg0(1) - 1); - break; - case 'b': // Repeat the preceding graphic character Ps times (REP). - if (mLastEmittedCodePoint == -1) break; - final int numRepeat = getArg0(1); - for (int i = 0; i < numRepeat; i++) emitCodePoint(mLastEmittedCodePoint); - break; - case 'c': // Primary Device Attributes (http://www.vt100.net/docs/vt510-rm/DA1) if argument is missing or zero. - // The important part that may still be used by some (tmux stores this value but does not currently use it) - // is the first response parameter identifying the terminal service class, where we send 64 for "vt420". - // This is followed by a list of attributes which is probably unused by applications. Send like xterm. - if (getArg0(0) == 0) mSession.write("\033[?64;1;2;6;9;15;18;21;22c"); - break; - case 'd': // ESC [ Pn d - Vert Position Absolute - setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1); - break; - case 'e': // Vertical Position Relative (VPR). From ISO-6429 (ECMA-48). - setCursorPosition(mCursorCol, mCursorRow + getArg0(1)); - break; - // case 'f': "${CSI}${ROW};${COLUMN}f" - Horizontal and Vertical Position (HVP). Grouped with case 'H'. - case 'g': // Clear menu_main stop - switch (getArg0(0)) { - case 0: - mTabStop[mCursorCol] = false; - break; - case 3: - for (int i = 0; i < mColumns; i++) { - mTabStop[i] = false; - } - break; - default: - // Specified to have no effect. - break; - } - break; - case 'h': // Set Mode - doSetMode(true); - break; - case 'l': // Reset Mode - doSetMode(false); - break; - case 'm': // Esc [ Pn m - character attributes. (can have up to 16 numerical arguments) - selectGraphicRendition(); - break; - case 'n': // Esc [ Pn n - ECMA-48 Status Report Commands - // sendDeviceAttributes() - switch (getArg0(0)) { - case 5: // Device status report (DSR): - // Answer is ESC [ 0 n (Terminal OK). - byte[] dsr = {(byte) 27, (byte) '[', (byte) '0', (byte) 'n'}; - mSession.write(dsr, 0, dsr.length); - break; - case 6: // Cursor position report (CPR): - // Answer is ESC [ y ; x R, where x,y is - // the cursorColor location. - mSession.write(String.format(Locale.US, "\033[%d;%dR", mCursorRow + 1, mCursorCol + 1)); - break; - default: - break; - } - break; - case 'r': // "CSI${top};${bottom}r" - set top and bottom Margins (DECSTBM). - { - // http://www.vt100.net/docs/vt510-rm/DECSTBM - // The top margin defaults to 1, the bottom margin defaults to mRows. - // The escape sequence numbers top 1..23, but we number top 0..22. - // The escape sequence numbers bottom 2..24, and so do we (because we use a zero based numbering - // scheme, but we store the first line below the bottom-most scrolling line. - // As a result, we adjust the top line by -1, but we leave the bottom line alone. - // Also require that top + 2 <= bottom. - mTopMargin = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2)); - mBottomMargin = Math.max(mTopMargin + 2, Math.min(getArg1(mRows), mRows)); - // DECSTBM moves the cursorColor to column 1, line 1 of the page respecting origin mode. - setCursorPosition(0, 0); - } - break; - case 's': - if (isDecsetInternalBitSet(DECSET_BIT_LEFTRIGHT_MARGIN_MODE)) { - // Set left and right margins (DECSLRM - http://www.vt100.net/docs/vt510-rm/DECSLRM). - mLeftMargin = Math.min(getArg0(1) - 1, mColumns - 2); - mRightMargin = Math.max(mLeftMargin + 1, Math.min(getArg1(mColumns), mColumns)); - // DECSLRM moves the cursorColor to column 1, line 1 of the page. - setCursorPosition(0, 0); - } else { - // Save cursorColor (ANSI.SYS), available only when DECLRMM is disabled. - saveCursor(); - } - break; - case 't': // Window manipulation (from dtterm, as well as extensions) - switch (getArg0(0)) { - case 11: // Report xterm window state. If the xterm window is open (non-iconified), it returns CSI 1 t . - mSession.write("\033[1t"); - break; - case 13: // Report xterm window position. Result is CSI 3 ; x ; y t - mSession.write("\033[3;0;0t"); - break; - case 14: // Report xterm window in pixels. Result is CSI 4 ; height ; width t - // We just report characters time 12 here. - mSession.write(String.format(Locale.US, "\033[4;%d;%dt", mRows * 12, mColumns * 12)); - break; - case 18: // Report the size of the text area in characters. Result is CSI 8 ; height ; width t - mSession.write(String.format(Locale.US, "\033[8;%d;%dt", mRows, mColumns)); - break; - case 19: // Report the size of the screen in characters. Result is CSI 9 ; height ; width t - // We report the same size as the view, since it's the view really isn't resizable from the shell. - mSession.write(String.format(Locale.US, "\033[9;%d;%dt", mRows, mColumns)); - break; - case 20: // Report xterm windows icon label. Result is OSC L label ST. Disabled due to security concerns: - mSession.write("\033]LIconLabel\033\\"); - break; - case 21: // Report xterm windows title. Result is OSC l label ST. Disabled due to security concerns: - mSession.write("\033]l\033\\"); - break; - case 22: - // 22;0 -> Save xterm icon and window title on stack. - // 22;1 -> Save xterm icon title on stack. - // 22;2 -> Save xterm window title on stack. - mTitleStack.push(mTitle); - if (mTitleStack.size() > 20) { - // Limit size - mTitleStack.remove(0); - } - break; - case 23: // Like 22 above but restore from stack. - if (!mTitleStack.isEmpty()) setTitle(mTitleStack.pop()); - break; - default: - // Ignore window manipulation. - break; - } - break; - case 'u': // Restore cursorColor (ANSI.SYS). - restoreCursor(); - break; - case ' ': - continueSequence(ESC_CSI_ARGS_SPACE); - break; - default: - parseArg(b); - break; - } - } - - /** - * Select Graphic Rendition (SGR) - see http://en.wikipedia.org/wiki/ANSI_escape_code#graphics. - */ - private void selectGraphicRendition() { + break; + case 'r': + case 's': if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; for (int i = 0; i <= mArgIndex; i++) { - int code = mArgs[i]; - if (code < 0) { - if (mArgIndex > 0) { - continue; - } else { - code = 0; - } - } - if (code == 0) { // reset - mForeColor = TextStyle.COLOR_INDEX_FOREGROUND; - mBackColor = TextStyle.COLOR_INDEX_BACKGROUND; - mEffect = 0; - } else if (code == 1) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_BOLD; - } else if (code == 2) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_DIM; - } else if (code == 3) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_ITALIC; - } else if (code == 4) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; - } else if (code == 5) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_BLINK; - } else if (code == 7) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_INVERSE; - } else if (code == 8) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE; - } else if (code == 9) { - mEffect |= TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH; - } else if (code == 10) { - // Exit alt charset (TERM=linux) - ignore. - } else if (code == 11) { - // Enter alt charset (TERM=linux) - ignore. - } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint. - mEffect &= ~(TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_DIM); - } else if (code == 23) { // not italic, but rarely used as such; clears standout with TERM=screen - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_ITALIC; - } else if (code == 24) { // underline: none - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; - } else if (code == 25) { // blink: none - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_BLINK; - } else if (code == 27) { // image: positive - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_INVERSE; - } else if (code == 28) { - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE; - } else if (code == 29) { - mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH; - } else if (code >= 30 && code <= 37) { - mForeColor = code - 30; - } else if (code == 38 || code == 48) { - // Extended set foregroundColor(38)/backgroundColor (48) color. - // This is followed by either "2;$R;$G;$B" to set a 24-bit color or - // "5;$INDEX" to set an indexed color. - if (i + 2 > mArgIndex) continue; - int firstArg = mArgs[i + 1]; - if (firstArg == 2) { - if (i + 4 > mArgIndex) { - Log.w(EmulatorDebug.LOG_TAG, "Too few CSI" + code + ";2 RGB arguments"); - } else { - int red = mArgs[i + 2], green = mArgs[i + 3], blue = mArgs[i + 4]; - if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) { - finishSequenceAndLogError("Invalid RGB: " + red + "," + green + "," + blue); - } else { - int argbColor = 0xff000000 | (red << 16) | (green << 8) | blue; - if (code == 38) { - mForeColor = argbColor; - } else { - mBackColor = argbColor; - } - } - i += 4; // "2;P_r;P_g;P_r" - } - } else if (firstArg == 5) { - int color = mArgs[i + 2]; - i += 2; // "5;P_s" - if (color >= 0 && color < TextStyle.NUM_INDEXED_COLORS) { - if (code == 38) { - mForeColor = color; - } else { - mBackColor = color; - } - } else { - if (LOG_ESCAPE_SEQUENCES) - Log.w(EmulatorDebug.LOG_TAG, "Invalid color index: " + color); - } - } else { - finishSequenceAndLogError("Invalid ISO-8613-3 SGR first argument: " + firstArg); - } - } else if (code == 39) { // Set default foregroundColor color. - mForeColor = TextStyle.COLOR_INDEX_FOREGROUND; - } else if (code >= 40 && code <= 47) { // Set backgroundColor color. - mBackColor = code - 40; - } else if (code == 49) { // Set default backgroundColor color. - mBackColor = TextStyle.COLOR_INDEX_BACKGROUND; - } else if (code >= 90 && code <= 97) { // Bright foregroundColor colors (aixterm codes). - mForeColor = code - 90 + 8; - } else if (code >= 100 && code <= 107) { // Bright backgroundColor color (aixterm codes). - mBackColor = code - 100 + 8; + int externalBit = mArgs[i]; + int internalBit = mapDecSetBitToInternalBit(externalBit); + if (internalBit == -1) { + Log.w(EmulatorDebug.LOG_TAG, "Ignoring request to save/recall decset bit=" + externalBit); + } else { + if (b == 's') { + mSavedDecSetFlags |= internalBit; } else { - if (LOG_ESCAPE_SEQUENCES) - Log.w(EmulatorDebug.LOG_TAG, String.format("SGR unknown code %d", code)); + doDecSetOrReset((mSavedDecSetFlags & internalBit) != 0, externalBit); } + } } + break; + case '$': + continueSequence(ESC_CSI_QUESTIONMARK_ARG_DOLLAR); + return; + default: + parseArg(b); } + } - private void doOsc(int b) { - switch (b) { - case 7: // Bell. - doOscSetTextParameters("\007"); - break; - case 27: // Escape. - continueSequence(ESC_OSC_ESC); - break; - default: - collectOSCArgs(b); - break; - } + public void doDecSetOrReset(boolean setting, int externalBit) { + int internalBit = mapDecSetBitToInternalBit(externalBit); + if (internalBit != -1) { + setDecsetinternalBit(internalBit, setting); } - - private void doOscEsc(int b) { - switch (b) { - case '\\': - doOscSetTextParameters("\033\\"); - break; - default: - // The ESC character was not followed by a \, so insert the ESC and - // the current character in arg buffer. - collectOSCArgs(27); - collectOSCArgs(b); - continueSequence(ESC_OSC); - break; - } - } - - /** - * An Operating System Controls (OSC) Set Text Parameters. May come here from BEL or ST. - */ - private void doOscSetTextParameters(String bellOrStringTerminator) { - int value = -1; - String textParameter = ""; - // Extract initial $value from initial "$value;..." string. - for (int mOSCArgTokenizerIndex = 0; mOSCArgTokenizerIndex < mOSCOrDeviceControlArgs.length(); mOSCArgTokenizerIndex++) { - char b = mOSCOrDeviceControlArgs.charAt(mOSCArgTokenizerIndex); - if (b == ';') { - textParameter = mOSCOrDeviceControlArgs.substring(mOSCArgTokenizerIndex + 1); - break; - } else if (b >= '0' && b <= '9') { - value = ((value < 0) ? 0 : value * 10) + (b - '0'); - } else { - unknownSequence(b); - return; - } - } - - switch (value) { - case 0: // Change icon name and window title to T. - case 1: // Change icon name to T. - case 2: // Change window title to T. - setTitle(textParameter); - break; - case 4: - // P s = 4 ; c ; spec → Change Color Number c to the color specified by spec. This can be a name or RGB - // specification as per XParseColor. Any number of c name pairs may be given. The color numbers correspond - // to the ANSI colors 0-7, their bright versions 8-15, and if supported, the remainder of the 88-color or - // 256-color table. - // If a "?" is given rather than a name or RGB specification, xterm replies with a control sequence of the - // same form which can be used to set the corresponding color. Because more than one pair of color number - // and specification can be given in one control sequence, xterm can make more than one reply. - int colorIndex = -1; - int parsingPairStart = -1; - for (int i = 0; ; i++) { - boolean endOfInput = i == textParameter.length(); - char b = endOfInput ? ';' : textParameter.charAt(i); - if (b == ';') { - if (parsingPairStart < 0) { - parsingPairStart = i + 1; - } else { - if (colorIndex < 0 || colorIndex > 255) { - unknownSequence(b); - return; - } else { - mColors.tryParseColor(colorIndex, textParameter.substring(parsingPairStart, i)); - mSession.onColorsChanged(); - colorIndex = -1; - parsingPairStart = -1; - } - } - } else if (parsingPairStart >= 0) { - // We have passed a color index and are now going through color spec. - } else if (parsingPairStart < 0 && (b >= '0' && b <= '9')) { - colorIndex = ((colorIndex < 0) ? 0 : colorIndex * 10) + (b - '0'); - } else { - unknownSequence(b); - return; - } - if (endOfInput) break; - } - break; - case 10: // Set foregroundColor color. - case 11: // Set backgroundColor color. - case 12: // Set cursorColor color. - int specialIndex = TextStyle.COLOR_INDEX_FOREGROUND + (value - 10); - int lastSemiIndex = 0; - for (int charIndex = 0; ; charIndex++) { - boolean endOfInput = charIndex == textParameter.length(); - if (endOfInput || textParameter.charAt(charIndex) == ';') { - try { - String colorSpec = textParameter.substring(lastSemiIndex, charIndex); - if ("?".equals(colorSpec)) { - // Report current color in the same format xterm and gnome-terminal does. - int rgb = mColors.mCurrentColors[specialIndex]; - int r = (65535 * ((rgb & 0x00FF0000) >> 16)) / 255; - int g = (65535 * ((rgb & 0x0000FF00) >> 8)) / 255; - int b = (65535 * ((rgb & 0x000000FF))) / 255; - mSession.write("\033]" + value + ";rgb:" + String.format(Locale.US, "%04x", r) + "/" + String.format(Locale.US, "%04x", g) + "/" - + String.format(Locale.US, "%04x", b) + bellOrStringTerminator); - } else { - mColors.tryParseColor(specialIndex, colorSpec); - mSession.onColorsChanged(); - } - specialIndex++; - if (endOfInput || (specialIndex > TextStyle.COLOR_INDEX_CURSOR) || ++charIndex >= textParameter.length()) - break; - lastSemiIndex = charIndex; - } catch (NumberFormatException e) { - // Ignore. - } - } - } - break; - case 52: // Manipulate Selection Data. Skip the optional first selection parameter(s). - int startIndex = textParameter.indexOf(";") + 1; - try { - String clipboardText = new String(Base64.decode(textParameter.substring(startIndex), 0), StandardCharsets.UTF_8); - mSession.clipboardText(clipboardText); - } catch (Exception e) { - Log.e(EmulatorDebug.LOG_TAG, "OSC Manipulate selection, invalid string '" + textParameter + ""); - } - break; - case 104: - // "104;$c" → Reset Color Number $c. It is reset to the color specified by the corresponding X - // resource. Any number of c parameters may be given. These parameters correspond to the ANSI colors 0-7, - // their bright versions 8-15, and if supported, the remainder of the 88-color or 256-color table. If no - // parameters are given, the entire table will be reset. - if (textParameter.isEmpty()) { - mColors.reset(); - mSession.onColorsChanged(); - } else { - int lastIndex = 0; - for (int charIndex = 0; ; charIndex++) { - boolean endOfInput = charIndex == textParameter.length(); - if (endOfInput || textParameter.charAt(charIndex) == ';') { - try { - int colorToReset = Integer.parseInt(textParameter.substring(lastIndex, charIndex)); - mColors.reset(colorToReset); - mSession.onColorsChanged(); - if (endOfInput) break; - charIndex++; - lastIndex = charIndex; - } catch (NumberFormatException e) { - // Ignore. - } - } - } - } - break; - case 110: // Reset foregroundColor color. - case 111: // Reset backgroundColor color. - case 112: // Reset cursorColor color. - mColors.reset(TextStyle.COLOR_INDEX_FOREGROUND + (value - 110)); - mSession.onColorsChanged(); - break; - case 119: // Reset highlight color. - break; - default: - unknownParameter(value); - break; - } - finishSequence(); - } - - private void blockClear(int sx, int sy, int w) { - blockClear(sx, sy, w, 1); - } - - private void blockClear(int sx, int sy, int w, int h) { - mScreen.blockSet(sx, sy, w, h, ' ', getStyle()); - } - - private long getStyle() { - return TextStyle.encode(mForeColor, mBackColor, mEffect); - } - - /** - * "CSI P_m h" for set or "CSI P_m l" for reset ANSI mode. - */ - private void doSetMode(boolean newValue) { - int modeBit = getArg0(0); - switch (modeBit) { - case 4: // Set="Insert Mode". Reset="Replace Mode". (IRM). - mInsertMode = newValue; - break; - case 20: // Normal Linefeed (LNM). - unknownParameter(modeBit); - // http://www.vt100.net/docs/vt510-rm/LNM - break; - case 34: - // Normal cursorColor visibility - when using TERM=screen, see - // http://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html - break; - default: - unknownParameter(modeBit); - break; - } - } - - /** - * NOTE: The parameters of this function respect the {@link #DECSET_BIT_ORIGIN_MODE}. Use - * {@link #setCursorRowCol(int, int)} for absolute pos. - */ - private void setCursorPosition(int x, int y) { - boolean originMode = isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE); - int effectiveTopMargin = originMode ? mTopMargin : 0; - int effectiveBottomMargin = originMode ? mBottomMargin : mRows; - int effectiveLeftMargin = originMode ? mLeftMargin : 0; - int effectiveRightMargin = originMode ? mRightMargin : mColumns; - int newRow = Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y, effectiveBottomMargin - 1)); - int newCol = Math.max(effectiveLeftMargin, Math.min(effectiveLeftMargin + x, effectiveRightMargin - 1)); - setCursorRowCol(newRow, newCol); - } - - private void scrollDownOneLine() { - mScrollCounter++; - if (mLeftMargin != 0 || mRightMargin != mColumns) { - // Horizontal margin: Do not put anything into scroll history, just non-margin part of screen up. - mScreen.blockCopy(mLeftMargin, mTopMargin + 1, mRightMargin - mLeftMargin, mBottomMargin - mTopMargin - 1, mLeftMargin, mTopMargin); - // .. and blank bottom row between margins: - mScreen.blockSet(mLeftMargin, mBottomMargin - 1, mRightMargin - mLeftMargin, 1, ' ', mEffect); - } else { - mScreen.scrollDownOneLine(mTopMargin, mBottomMargin, getStyle()); - } - } - - /** - * Process the next ASCII character of a parameter. - */ - private void parseArg(int b) { - if (b >= '0' && b <= '9') { - if (mArgIndex < mArgs.length) { - int oldValue = mArgs[mArgIndex]; - int thisDigit = b - '0'; - int value; - if (oldValue >= 0) { - value = oldValue * 10 + thisDigit; - } else { - value = thisDigit; - } - mArgs[mArgIndex] = value; - } - continueSequence(mEscapeState); - } else if (b == ';') { - if (mArgIndex < mArgs.length) { - mArgIndex++; - } - continueSequence(mEscapeState); - } else { - unknownSequence(b); - } - } - - private int getArg0(int defaultValue) { - return getArg(0, defaultValue, true); - } - - private int getArg1(int defaultValue) { - return getArg(1, defaultValue, true); - } - - private int getArg(int index, int defaultValue, boolean treatZeroAsDefault) { - int result = mArgs[index]; - if (result < 0 || (result == 0 && treatZeroAsDefault)) { - result = defaultValue; - } - return result; - } - - private void collectOSCArgs(int b) { - if (mOSCOrDeviceControlArgs.length() < MAX_OSC_STRING_LENGTH) { - mOSCOrDeviceControlArgs.appendCodePoint(b); - continueSequence(mEscapeState); - } else { - unknownSequence(b); - } - } - - private void unimplementedSequence(int b) { - logError("Unimplemented sequence char '" + (char) b + "' (U+" + String.format("%04x", b) + ")"); - finishSequence(); - } - - private void unknownSequence(int b) { - logError("Unknown sequence char '" + (char) b + "' (numeric value=" + b + ")"); - finishSequence(); - } - - private void unknownParameter(int parameter) { - logError("Unknown parameter: " + parameter); - finishSequence(); - } - - private void logError(String errorType) { - if (LOG_ESCAPE_SEQUENCES) { - StringBuilder buf = new StringBuilder(); - buf.append(errorType); - buf.append(", escapeState="); - buf.append(mEscapeState); - boolean firstArg = true; - if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; - for (int i = 0; i <= mArgIndex; i++) { - int value = mArgs[i]; - if (value >= 0) { - if (firstArg) { - firstArg = false; - buf.append(", args={"); - } else { - buf.append(','); - } - buf.append(value); - } - } - if (!firstArg) buf.append('}'); - finishSequenceAndLogError(buf.toString()); - } - } - - private void finishSequenceAndLogError(String error) { - if (LOG_ESCAPE_SEQUENCES) Log.w(EmulatorDebug.LOG_TAG, error); - finishSequence(); - } - - private void finishSequence() { - mEscapeState = ESC_NONE; - } - - /** - * Send a Unicode code point to the screen. - * - * @param codePoint The code point of the character to display - */ - private void emitCodePoint(int codePoint) { - mLastEmittedCodePoint = codePoint; - if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) { - // http://www.vt100.net/docs/vt102-ug/table5-15.html. - switch (codePoint) { - case '_': - codePoint = ' '; // Blank. - break; - case '`': - codePoint = '◆'; // Diamond. - break; - case '0': - codePoint = '█'; // Solid block; - break; - case 'a': - codePoint = '▒'; // Checker board. - break; - case 'b': - codePoint = '␉'; // Horizontal menu_main. - break; - case 'c': - codePoint = '␌'; // Form feed. - break; - case 'd': - codePoint = '\r'; // Carriage return. - break; - case 'e': - codePoint = '␊'; // Linefeed. - break; - case 'f': - codePoint = '°'; // Degree. - break; - case 'g': - codePoint = '±'; // Plus-minus. - break; - case 'h': - codePoint = '\n'; // Newline. - break; - case 'i': - codePoint = '␋'; // Vertical menu_main. - break; - case 'j': - codePoint = '┘'; // Lower right corner. - break; - case 'k': - codePoint = '┐'; // Upper right corner. - break; - case 'l': - codePoint = '┌'; // Upper left corner. - break; - case 'm': - codePoint = '└'; // Left left corner. - break; - case 'n': - codePoint = '┼'; // Crossing lines. - break; - case 'o': - codePoint = '⎺'; // Horizontal line - scan 1. - break; - case 'p': - codePoint = '⎻'; // Horizontal line - scan 3. - break; - case 'q': - codePoint = '─'; // Horizontal line - scan 5. - break; - case 'r': - codePoint = '⎼'; // Horizontal line - scan 7. - break; - case 's': - codePoint = '⎽'; // Horizontal line - scan 9. - break; - case 't': - codePoint = '├'; // T facing rightwards. - break; - case 'u': - codePoint = '┤'; // T facing leftwards. - break; - case 'v': - codePoint = '┴'; // T facing upwards. - break; - case 'w': - codePoint = '┬'; // T facing downwards. - break; - case 'x': - codePoint = '│'; // Vertical line. - break; - case 'y': - codePoint = '≤'; // Less than or equal to. - break; - case 'z': - codePoint = '≥'; // Greater than or equal to. - break; - case '{': - codePoint = 'π'; // Pi. - break; - case '|': - codePoint = '≠'; // Not equal to. - break; - case '}': - codePoint = '£'; // UK pound. - break; - case '~': - codePoint = '·'; // Centered dot. - break; - } - } - - final boolean autoWrap = isDecsetInternalBitSet(DECSET_BIT_AUTOWRAP); - final int displayWidth = WcWidth.width(codePoint); - final boolean cursorInLastColumn = mCursorCol == mRightMargin - 1; - - if (autoWrap) { - if (cursorInLastColumn && ((mAboutToAutoWrap && displayWidth == 1) || displayWidth == 2)) { - mScreen.setLineWrap(mCursorRow); - mCursorCol = mLeftMargin; - if (mCursorRow + 1 < mBottomMargin) { - mCursorRow++; - } else { - scrollDownOneLine(); - } - } - } else if (cursorInLastColumn && displayWidth == 2) { - // The behaviour when a wide character is output with cursorColor in the last column when - // autowrap is disabled is not obvious - it's ignored here. - return; - } - - if (mInsertMode && displayWidth > 0) { - // Move character to right one space. - int destCol = mCursorCol + displayWidth; - if (destCol < mRightMargin) - mScreen.blockCopy(mCursorCol, mCursorRow, mRightMargin - destCol, 1, destCol, mCursorRow); - } - - int offsetDueToCombiningChar = ((displayWidth <= 0 && mCursorCol > 0 && !mAboutToAutoWrap) ? 1 : 0); - mScreen.setChar(mCursorCol - offsetDueToCombiningChar, mCursorRow, codePoint, getStyle()); - - if (autoWrap && displayWidth > 0) - mAboutToAutoWrap = (mCursorCol == mRightMargin - displayWidth); - - mCursorCol = Math.min(mCursorCol + displayWidth, mRightMargin - 1); - } - - private void setCursorRow(int row) { - mCursorRow = row; - mAboutToAutoWrap = false; - } - - private void setCursorCol(int col) { - mCursorCol = col; - mAboutToAutoWrap = false; - } - - /** - * Set the cursorColor mode, but limit it to margins if {@link #DECSET_BIT_ORIGIN_MODE} is enabled. - */ - private void setCursorColRespectingOriginMode(int col) { - setCursorPosition(col, mCursorRow); - } - - /** - * TODO: Better name, distinguished from {@link #setCursorPosition(int, int)} by not regarding origin mode. - */ - private void setCursorRowCol(int row, int col) { - mCursorRow = Math.max(0, Math.min(row, mRows - 1)); - mCursorCol = Math.max(0, Math.min(col, mColumns - 1)); - mAboutToAutoWrap = false; - } - - public int getScrollCounter() { - return mScrollCounter; - } - - public void clearScrollCounter() { - mScrollCounter = 0; - } - - /** - * Reset terminal state so user can interact with it regardless of present state. - */ - public void reset() { - mCursorStyle = CURSOR_STYLE_BLOCK; - mArgIndex = 0; - mContinueSequence = false; - mEscapeState = ESC_NONE; - mInsertMode = false; - mTopMargin = mLeftMargin = 0; + switch (externalBit) { + case 1: // Application Cursor Keys (DECCKM). + break; + case 3: // Set: 132 column mode (. Reset: 80 column mode. ANSI name: DECCOLM. + // We don't actually set/reset 132 cols, but we do want the side effects + // (FIXME: Should only do this if the 95 DECSET bit (DECNCSM) is set, and if changing value?): + // Sets the left, right, top and bottom scrolling margins to their default positions, which is important for + // the "reset" utility to really reset the terminal: + mLeftMargin = mTopMargin = 0; mBottomMargin = mRows; mRightMargin = mColumns; - mAboutToAutoWrap = false; - mForeColor = mSavedStateMain.mSavedForeColor = mSavedStateAlt.mSavedForeColor = TextStyle.COLOR_INDEX_FOREGROUND; - mBackColor = mSavedStateMain.mSavedBackColor = mSavedStateAlt.mSavedBackColor = TextStyle.COLOR_INDEX_BACKGROUND; - setDefaultTabStops(); - - mUseLineDrawingG0 = mUseLineDrawingG1 = false; - mUseLineDrawingUsesG0 = true; - - mSavedStateMain.mSavedCursorRow = mSavedStateMain.mSavedCursorCol = mSavedStateMain.mSavedEffect = mSavedStateMain.mSavedDecFlags = 0; - mSavedStateAlt.mSavedCursorRow = mSavedStateAlt.mSavedCursorCol = mSavedStateAlt.mSavedEffect = mSavedStateAlt.mSavedDecFlags = 0; - mCurrentDecSetFlags = 0; - // Initial wrap-around is not accurate but makes terminal more useful, especially on a small screen: - setDecsetinternalBit(DECSET_BIT_AUTOWRAP, true); - setDecsetinternalBit(DECSET_BIT_SHOWING_CURSOR, true); - mSavedDecSetFlags = mSavedStateMain.mSavedDecFlags = mSavedStateAlt.mSavedDecFlags = mCurrentDecSetFlags; - - // XXX: Should we set terminal driver back to IUTF8 with termios? - mUtf8Index = mUtf8ToFollow = 0; - - mColors.reset(); - mSession.onColorsChanged(); - } - - public void setColorScheme(TerminalColorScheme colorScheme) { - mColors.reset(colorScheme); - mSession.onColorsChanged(); - } - - public String getSelectedText(int x1, int y1, int x2, int y2) { - return mScreen.getSelectedText(x1, y1, x2, y2); - } - - /** - * Get the terminal session's title (null if not set). - */ - public String getTitle() { - return mTitle; - } - - /** - * Change the terminal session's title. - */ - private void setTitle(String newTitle) { - String oldTitle = mTitle; - mTitle = newTitle; - if (!Objects.equals(oldTitle, newTitle)) { - mSession.titleChanged(oldTitle, newTitle); + // "DECCOLM resets vertical split screen mode (DECLRMM) to unavailable": + setDecsetinternalBit(DECSET_BIT_LEFTRIGHT_MARGIN_MODE, false); + // "Erases all data in page memory": + blockClear(0, 0, mColumns, mRows); + setCursorRowCol(0, 0); + break; + case 4: // DECSCLM-Scrolling Mode. Ignore. + break; + case 5: // Reverse video. No action. + break; + case 6: // Set: Origin Mode. Reset: Normal Cursor Mode. Ansi name: DECOM. + if (setting) setCursorPosition(0, 0); + break; + case 7: // Wrap-around bit, not specific action. + case 8: // Auto-repeat Keys (DECARM). Do not implement. + case 9: // X10 mouse reporting - outdated. Do not implement. + case 12: // Control cursorColor blinking - ignore. + case 25: // Hide/show cursorColor - no action needed, renderer will check with isShowingCursor(). + case 40: // Allow 80 => 132 Mode, ignore. + case 45: // TODO: Reverse wrap-around. Implement??? + case 66: // Application keypad (DECNKM). + break; + case 69: // Left and right margin mode (DECLRMM). + if (!setting) { + mLeftMargin = 0; + mRightMargin = mColumns; } + break; + case 1000: + case 1001: + case 1002: + case 1003: + case 1004: + case 1005: // UTF-8 mouse mode, ignore. + case 1006: // SGR Mouse Mode + case 1015: + case 1034: // Interpret "meta" key, sets eighth bit. + break; + case 1048: // Set: Save cursorColor as in DECSC. Reset: Restore cursorColor as in DECRC. + if (setting) + saveCursor(); + else + restoreCursor(); + break; + case 47: + case 1047: + case 1049: { + // Set: Save cursorColor as in DECSC and use Alternate Screen Buffer, clearing it first. + // Reset: Use Normal Screen Buffer and restore cursorColor as in DECRC. + TerminalBuffer newScreen = setting ? mAltBuffer : mMainBuffer; + if (newScreen != mScreen) { + boolean resized = !(newScreen.mColumns == mColumns && newScreen.mScreenRows == mRows); + if (setting) saveCursor(); + mScreen = newScreen; + if (!setting) { + int col = mSavedStateMain.mSavedCursorCol; + int row = mSavedStateMain.mSavedCursorRow; + restoreCursor(); + if (resized) { + // Restore cursorColor position _not_ clipped to current screen (let resizeScreen() handle that): + mCursorCol = col; + mCursorRow = row; + } + } + // Check if buffer size needs to be updated: + if (resized) resizeScreen(); + // Clear new screen if alt buffer: + if (newScreen == mAltBuffer) + newScreen.blockSet(0, 0, mColumns, mRows, ' ', getStyle()); + } + break; + } + case 2004: + // Bracketed paste mode - setting bit is enough. + break; + default: + unknownParameter(externalBit); + break; + } + } + + private void doCsiBiggerThan(int b) { + switch (b) { + case 'c': // "${CSI}>c" or "${CSI}>c". Secondary Device Attributes (DA2). + // Originally this was used for the terminal to respond with "identification code, firmware version level, + // and hardware options" (http://vt100.net/docs/vt510-rm/DA2), with the first "41" meaning the VT420 + // terminal type. This is not used anymore, but the second version level field has been changed by xterm + // to mean it's release number ("patch numbers" listed at http://invisible-island.net/xterm/xterm.log.html), + // and some applications use it as a feature check: + // * tmux used to have a "xterm won't reach version 500 for a while so set that as the upper limit" check, + // and then check "xterm_version > 270" if rectangular area operations such as DECCRA could be used. + // * vim checks xterm version number >140 for "Request termcap/terminfo string" functionality >276 for SGR + // mouse report. + // The third number is a keyboard identifier not used nowadays. + mSession.write("\033[>41;320;0c"); + break; + case 'm': + // https://bugs.launchpad.net/gnome-terminal/+bug/96676/comments/25 + // Depending on the first number parameter, this can set one of the xterm resources + // modifyKeyboard, modifyCursorKeys, modifyFunctionKeys and modifyOtherKeys. + // http://invisible-island.net/xterm/manpage/xterm.html#RESOURCES + + // * modifyKeyboard (parameter=1): + // Normally xterm makes a special case regarding modifiers (shift, control, etc.) to handle special keyboard + // layouts (legacy and vt220). This is done to provide compatible keyboards for DEC VT220 and related + // terminals that implement user-defined keys (UDK). + // The bits of the resource value selectively enable modification of the given category when these keyboards + // are selected. The default is "0": + // (0) The legacy/vt220 keyboards interpret only the Control-modifier when constructing numbered + // function-keys. Other special keys are not modified. + // (1) allows modification of the numeric keypad + // (2) allows modification of the editing keypad + // (4) allows modification of function-keys, overrides use of Shift-modifier for UDK. + // (8) allows modification of other special keys + + // * modifyCursorKeys (parameter=2): + // Tells how to handle the special case where Control-, Shift-, Alt- or Meta-modifiers are used to add a + // parameter to the escape sequence returned by a cursorColor-key. The default is "2". + // - Set it to -1 to disable it. + // - Set it to 0 to use the old/obsolete behavior. + // - Set it to 1 to prefix modified sequences with CSI. + // - Set it to 2 to force the modifier to be the second parameter if it would otherwise be the first. + // - Set it to 3 to mark the sequence with a ">" to hint that it is private. + + // * modifyFunctionKeys (parameter=3): + // Tells how to handle the special case where Control-, Shift-, Alt- or Meta-modifiers are used to add a + // parameter to the escape sequence returned by a (numbered) function- + // key. The default is "2". The resource values are similar to modifyCursorKeys: + // Set it to -1 to permit the user to use shift- and control-modifiers to construct function-key strings + // using the normal encoding scheme. + // - Set it to 0 to use the old/obsolete behavior. + // - Set it to 1 to prefix modified sequences with CSI. + // - Set it to 2 to force the modifier to be the second parameter if it would otherwise be the first. + // - Set it to 3 to mark the sequence with a ">" to hint that it is private. + // If modifyFunctionKeys is zero, xterm uses Control- and Shift-modifiers to allow the user to construct + // numbered function-keys beyond the set provided by the keyboard: + // (Control) adds the value given by the ctrlFKeys resource. + // (Shift) adds twice the value given by the ctrlFKeys resource. + // (Control/Shift) adds three times the value given by the ctrlFKeys resource. + // + // As a special case, legacy (when oldFunctionKeys is true) or vt220 (when sunKeyboard is true) + // keyboards interpret only the Control-modifier when constructing numbered function-keys. + // This is done to provide compatible keyboards for DEC VT220 and related terminals that + // implement user-defined keys (UDK). + + // * modifyOtherKeys (parameter=4): + // Like modifyCursorKeys, tells xterm to construct an escape sequence for other keys (such as "2") when + // modified by Control-, Alt- or Meta-modifiers. This feature does not apply to function keys and + // well-defined keys such as ESC or the control keys. The default is "0". + // (0) disables this feature. + // (1) enables this feature for keys except for those with well-known behavior, e.g., Tab, Backarrow and + // some special control character cases, e.g., Control-Space to make a NUL. + // (2) enables this feature for keys including the exceptions listed. + Log.e(EmulatorDebug.LOG_TAG, "(ignored) CSI > MODIFY RESOURCE: " + getArg0(-1) + " to " + getArg1(-1)); + break; + default: + parseArg(b); + break; + } + } + + private void startEscapeSequence() { + mEscapeState = ESC; + mArgIndex = 0; + Arrays.fill(mArgs, -1); + } + + private void doLinefeed() { + boolean belowScrollingRegion = mCursorRow >= mBottomMargin; + int newCursorRow = mCursorRow + 1; + if (belowScrollingRegion) { + // Move down (but not scroll) as long as we are above the last row. + if (mCursorRow != mRows - 1) { + setCursorRow(newCursorRow); + } + } else { + if (newCursorRow == mBottomMargin) { + scrollDownOneLine(); + newCursorRow = mBottomMargin - 1; + } + setCursorRow(newCursorRow); + } + } + + private void continueSequence(int state) { + mEscapeState = state; + mContinueSequence = true; + } + + private void doEscPound(int b) { + switch (b) { + case '8': // Esc # 8 - DEC screen alignment test - fill screen with E's. + mScreen.blockSet(0, 0, mColumns, mRows, 'E', getStyle()); + break; + default: + unknownSequence(b); + break; + } + } + + /** + * Encountering a character in the {@link #ESC} state. + */ + private void doEsc(int b) { + switch (b) { + case '#': + continueSequence(ESC_POUND); + break; + case '(': + continueSequence(ESC_SELECT_LEFT_PAREN); + break; + case ')': + continueSequence(ESC_SELECT_RIGHT_PAREN); + break; + case '6': // Back index (http://www.vt100.net/docs/vt510-rm/DECBI). Move left, insert blank column if start. + if (mCursorCol > mLeftMargin) { + mCursorCol--; + } else { + int rows = mBottomMargin - mTopMargin; + mScreen.blockCopy(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin - 1, rows, mLeftMargin + 1, mTopMargin); + mScreen.blockSet(mLeftMargin, mTopMargin, 1, rows, ' ', TextStyle.encode(mForeColor, mBackColor, 0)); + } + break; + case '7': // DECSC save cursorColor - http://www.vt100.net/docs/vt510-rm/DECSC + saveCursor(); + break; + case '8': // DECRC restore cursorColor - http://www.vt100.net/docs/vt510-rm/DECRC + restoreCursor(); + break; + case '9': // Forward Index (http://www.vt100.net/docs/vt510-rm/DECFI). Move right, insert blank column if end. + if (mCursorCol < mRightMargin - 1) { + mCursorCol++; + } else { + int rows = mBottomMargin - mTopMargin; + mScreen.blockCopy(mLeftMargin + 1, mTopMargin, mRightMargin - mLeftMargin - 1, rows, mLeftMargin, mTopMargin); + mScreen.blockSet(mRightMargin - 1, mTopMargin, 1, rows, ' ', TextStyle.encode(mForeColor, mBackColor, 0)); + } + break; + case 'c': // RIS - Reset to Initial State (http://vt100.net/docs/vt510-rm/RIS). + reset(); + blockClear(0, 0, mColumns, mRows); + setCursorPosition(0, 0); + break; + case 'D': // INDEX + doLinefeed(); + break; + case 'E': // Next line (http://www.vt100.net/docs/vt510-rm/NEL). + setCursorCol(isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE) ? mLeftMargin : 0); + doLinefeed(); + break; + case 'F': // Cursor to lower-left corner of screen + setCursorRowCol(0, mBottomMargin - 1); + break; + case 'H': // Tab set + mTabStop[mCursorCol] = true; + break; + case 'M': // "${ESC}M" - reverse index (RI). + // http://www.vt100.net/docs/vt100-ug/chapter3.html: "Move the active position to the same horizontal + // position on the preceding line. If the active position is at the top margin, a scroll down is performed". + if (mCursorRow <= mTopMargin) { + mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin - (mTopMargin + 1), 0, mTopMargin + 1); + blockClear(0, mTopMargin, mColumns); + } else { + mCursorRow--; + } + break; + case 'N': // SS2, ignore. + case '0': // SS3, ignore. + break; + case 'P': // Device control string + mOSCOrDeviceControlArgs.setLength(0); + continueSequence(ESC_P); + break; + case '[': + continueSequence(ESC_CSI); + break; + case '=': // DECKPAM + setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, true); + break; + case ']': // OSC + mOSCOrDeviceControlArgs.setLength(0); + continueSequence(ESC_OSC); + break; + case '>': // DECKPNM + setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, false); + break; + default: + unknownSequence(b); + break; + } + } + + /** + * DECSC save cursorColor - http://www.vt100.net/docs/vt510-rm/DECSC . See {@link #restoreCursor()}. + */ + private void saveCursor() { + SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt; + state.mSavedCursorRow = mCursorRow; + state.mSavedCursorCol = mCursorCol; + state.mSavedEffect = mEffect; + state.mSavedForeColor = mForeColor; + state.mSavedBackColor = mBackColor; + state.mSavedDecFlags = mCurrentDecSetFlags; + state.mUseLineDrawingG0 = mUseLineDrawingG0; + state.mUseLineDrawingG1 = mUseLineDrawingG1; + state.mUseLineDrawingUsesG0 = mUseLineDrawingUsesG0; + } + + /** + * DECRS restore cursorColor - http://www.vt100.net/docs/vt510-rm/DECRC. See {@link #saveCursor()}. + */ + private void restoreCursor() { + SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt; + setCursorRowCol(state.mSavedCursorRow, state.mSavedCursorCol); + mEffect = state.mSavedEffect; + mForeColor = state.mSavedForeColor; + mBackColor = state.mSavedBackColor; + int mask = (DECSET_BIT_AUTOWRAP | DECSET_BIT_ORIGIN_MODE); + mCurrentDecSetFlags = (mCurrentDecSetFlags & ~mask) | (state.mSavedDecFlags & mask); + mUseLineDrawingG0 = state.mUseLineDrawingG0; + mUseLineDrawingG1 = state.mUseLineDrawingG1; + mUseLineDrawingUsesG0 = state.mUseLineDrawingUsesG0; + } + + /** + * Following a CSI - Control Sequence Introducer, "\033[". {@link #ESC_CSI}. + */ + private void doCsi(int b) { + switch (b) { + case '!': + continueSequence(ESC_CSI_EXCLAMATION); + break; + case '"': + continueSequence(ESC_CSI_DOUBLE_QUOTE); + break; + case '\'': + continueSequence(ESC_CSI_SINGLE_QUOTE); + break; + case '$': + continueSequence(ESC_CSI_DOLLAR); + break; + case '*': + continueSequence(ESC_CSI_ARGS_ASTERIX); + break; + case '@': { + // "CSI{n}@" - Insert ${n} space characters (ICH) - http://www.vt100.net/docs/vt510-rm/ICH. + mAboutToAutoWrap = false; + int columnsAfterCursor = mColumns - mCursorCol; + int spacesToInsert = Math.min(getArg0(1), columnsAfterCursor); + int charsToMove = columnsAfterCursor - spacesToInsert; + mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1, mCursorCol + spacesToInsert, mCursorRow); + blockClear(mCursorCol, mCursorRow, spacesToInsert); + } + break; + case 'A': // "CSI${n}A" - Cursor up (CUU) ${n} rows. + setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1))); + break; + case 'B': // "CSI${n}B" - Cursor down (CUD) ${n} rows. + setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1))); + break; + case 'C': // "CSI${n}C" - Cursor forward (CUF). + case 'a': // "CSI${n}a" - Horizontal position relative (HPR). From ISO-6428/ECMA-48. + setCursorCol(Math.min(mRightMargin - 1, mCursorCol + getArg0(1))); + break; + case 'D': // "CSI${n}D" - Cursor backward (CUB) ${n} columns. + setCursorCol(Math.max(mLeftMargin, mCursorCol - getArg0(1))); + break; + case 'E': // "CSI{n}E - Cursor Next Line (CNL). From ISO-6428/ECMA-48. + setCursorPosition(0, mCursorRow + getArg0(1)); + break; + case 'F': // "CSI{n}F - Cursor Previous Line (CPL). From ISO-6428/ECMA-48. + setCursorPosition(0, mCursorRow - getArg0(1)); + break; + case 'G': // "CSI${n}G" - Cursor horizontal absolute (CHA) to column ${n}. + setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1); + break; + case 'H': // "${CSI}${ROW};${COLUMN}H" - Cursor position (CUP). + case 'f': // "${CSI}${ROW};${COLUMN}f" - Horizontal and Vertical Position (HVP). + setCursorPosition(getArg1(1) - 1, getArg0(1) - 1); + break; + case 'I': // Cursor Horizontal Forward Tabulation (CHT). Move the active position n tabs forward. + setCursorCol(nextTabStop(getArg0(1))); + break; + case 'J': // "${CSI}${0,1,2}J" - Erase in Display (ED) + // ED ignores the scrolling margins. + switch (getArg0(0)) { + case 0: // Erase from the active position to the end of the screen, inclusive (default). + blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); + blockClear(0, mCursorRow + 1, mColumns, mRows - (mCursorRow + 1)); + break; + case 1: // Erase from start of the screen to the active position, inclusive. + blockClear(0, 0, mColumns, mCursorRow); + blockClear(0, mCursorRow, mCursorCol + 1); + break; + case 2: // Erase all of the display - all lines are erased, changed to single-width, and the cursorColor does not + // move.. + blockClear(0, 0, mColumns, mRows); + break; + default: + unknownSequence(b); + return; + } + mAboutToAutoWrap = false; + break; + case 'K': // "CSI{n}K" - Erase in line (EL). + switch (getArg0(0)) { + case 0: // Erase from the cursorColor to the end of the line, inclusive (default) + blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); + break; + case 1: // Erase from the start of the screen to the cursorColor, inclusive. + blockClear(0, mCursorRow, mCursorCol + 1); + break; + case 2: // Erase all of the line. + blockClear(0, mCursorRow, mColumns); + break; + default: + unknownSequence(b); + return; + } + mAboutToAutoWrap = false; + break; + case 'L': // "${CSI}{N}L" - insert ${N} lines (IL). + { + int linesAfterCursor = mBottomMargin - mCursorRow; + int linesToInsert = Math.min(getArg0(1), linesAfterCursor); + int linesToMove = linesAfterCursor - linesToInsert; + mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0, mCursorRow + linesToInsert); + blockClear(0, mCursorRow, mColumns, linesToInsert); + } + break; + case 'M': // "${CSI}${N}M" - delete N lines (DL). + { + mAboutToAutoWrap = false; + int linesAfterCursor = mBottomMargin - mCursorRow; + int linesToDelete = Math.min(getArg0(1), linesAfterCursor); + int linesToMove = linesAfterCursor - linesToDelete; + mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns, linesToMove, 0, mCursorRow); + blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete); + } + break; + case 'P': // "${CSI}{N}P" - delete ${N} characters (DCH). + { + // http://www.vt100.net/docs/vt510-rm/DCH: "If ${N} is greater than the number of characters between the + // cursorColor and the right margin, then DCH only deletes the remaining characters. + // As characters are deleted, the remaining characters between the cursorColor and right margin move to the left. + // Character attributes move with the characters. The terminal adds blank spaces with no visual character + // attributes at the right margin. DCH has no effect outside the scrolling margins." + mAboutToAutoWrap = false; + int cellsAfterCursor = mColumns - mCursorCol; + int cellsToDelete = Math.min(getArg0(1), cellsAfterCursor); + int cellsToMove = cellsAfterCursor - cellsToDelete; + mScreen.blockCopy(mCursorCol + cellsToDelete, mCursorRow, cellsToMove, 1, mCursorCol, mCursorRow); + blockClear(mCursorCol + cellsToMove, mCursorRow, cellsToDelete); + } + break; + case 'S': { // "${CSI}${N}S" - scroll up ${N} lines (default = 1) (SU). + final int linesToScroll = getArg0(1); + for (int i = 0; i < linesToScroll; i++) + scrollDownOneLine(); + break; + } + case 'T': + if (mArgIndex == 0) { + // "${CSI}${N}T" - Scroll down N lines (default = 1) (SD). + // http://vt100.net/docs/vt510-rm/SD: "N is the number of lines to move the user window up in page + // memory. N new lines appear at the top of the display. N old lines disappear at the bottom of the + // display. You cannot pan past the top margin of the current page". + final int linesToScrollArg = getArg0(1); + final int linesBetweenTopAndBottomMargins = mBottomMargin - mTopMargin; + final int linesToScroll = Math.min(linesBetweenTopAndBottomMargins, linesToScrollArg); + mScreen.blockCopy(0, mTopMargin, mColumns, linesBetweenTopAndBottomMargins - linesToScroll, 0, mTopMargin + linesToScroll); + blockClear(0, mTopMargin, mColumns, linesToScroll); + } else { + // "${CSI}${func};${startx};${starty};${firstrow};${lastrow}T" - initiate highlight mouse tracking. + unimplementedSequence(b); + } + break; + case 'X': // "${CSI}${N}X" - Erase ${N:=1} character(s) (ECH). FIXME: Clears character attributes? + mAboutToAutoWrap = false; + mScreen.blockSet(mCursorCol, mCursorRow, Math.min(getArg0(1), mColumns - mCursorCol), 1, ' ', getStyle()); + break; + case 'Z': // Cursor Backward Tabulation (CBT). Move the active position n tabs backward. + int numberOfTabs = getArg0(1); + int newCol = mLeftMargin; + for (int i = mCursorCol - 1; i >= 0; i--) + if (mTabStop[i]) { + if (--numberOfTabs == 0) { + newCol = Math.max(i, mLeftMargin); + break; + } + } + mCursorCol = newCol; + break; + case '?': // Esc [ ? -- start of a private mode set + continueSequence(ESC_CSI_QUESTIONMARK); + break; + case '>': // "Esc [ >" -- + continueSequence(ESC_CSI_BIGGERTHAN); + break; + case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA). + setCursorColRespectingOriginMode(getArg0(1) - 1); + break; + case 'b': // Repeat the preceding graphic character Ps times (REP). + if (mLastEmittedCodePoint == -1) break; + final int numRepeat = getArg0(1); + for (int i = 0; i < numRepeat; i++) emitCodePoint(mLastEmittedCodePoint); + break; + case 'c': // Primary Device Attributes (http://www.vt100.net/docs/vt510-rm/DA1) if argument is missing or zero. + // The important part that may still be used by some (tmux stores this value but does not currently use it) + // is the first response parameter identifying the terminal service class, where we send 64 for "vt420". + // This is followed by a list of attributes which is probably unused by applications. Send like xterm. + if (getArg0(0) == 0) mSession.write("\033[?64;1;2;6;9;15;18;21;22c"); + break; + case 'd': // ESC [ Pn d - Vert Position Absolute + setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1); + break; + case 'e': // Vertical Position Relative (VPR). From ISO-6429 (ECMA-48). + setCursorPosition(mCursorCol, mCursorRow + getArg0(1)); + break; + // case 'f': "${CSI}${ROW};${COLUMN}f" - Horizontal and Vertical Position (HVP). Grouped with case 'H'. + case 'g': // Clear menu_main stop + switch (getArg0(0)) { + case 0: + mTabStop[mCursorCol] = false; + break; + case 3: + for (int i = 0; i < mColumns; i++) { + mTabStop[i] = false; + } + break; + default: + // Specified to have no effect. + break; + } + break; + case 'h': // Set Mode + doSetMode(true); + break; + case 'l': // Reset Mode + doSetMode(false); + break; + case 'm': // Esc [ Pn m - character attributes. (can have up to 16 numerical arguments) + selectGraphicRendition(); + break; + case 'n': // Esc [ Pn n - ECMA-48 Status Report Commands + // sendDeviceAttributes() + switch (getArg0(0)) { + case 5: // Device status report (DSR): + // Answer is ESC [ 0 n (Terminal OK). + byte[] dsr = {(byte) 27, (byte) '[', (byte) '0', (byte) 'n'}; + mSession.write(dsr, 0, dsr.length); + break; + case 6: // Cursor position report (CPR): + // Answer is ESC [ y ; x R, where x,y is + // the cursorColor location. + mSession.write(String.format(Locale.US, "\033[%d;%dR", mCursorRow + 1, mCursorCol + 1)); + break; + default: + break; + } + break; + case 'r': // "CSI${top};${bottom}r" - set top and bottom Margins (DECSTBM). + { + // http://www.vt100.net/docs/vt510-rm/DECSTBM + // The top margin defaults to 1, the bottom margin defaults to mRows. + // The escape sequence numbers top 1..23, but we number top 0..22. + // The escape sequence numbers bottom 2..24, and so do we (because we use a zero based numbering + // scheme, but we store the first line below the bottom-most scrolling line. + // As a result, we adjust the top line by -1, but we leave the bottom line alone. + // Also require that top + 2 <= bottom. + mTopMargin = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2)); + mBottomMargin = Math.max(mTopMargin + 2, Math.min(getArg1(mRows), mRows)); + // DECSTBM moves the cursorColor to column 1, line 1 of the page respecting origin mode. + setCursorPosition(0, 0); + } + break; + case 's': + if (isDecsetInternalBitSet(DECSET_BIT_LEFTRIGHT_MARGIN_MODE)) { + // Set left and right margins (DECSLRM - http://www.vt100.net/docs/vt510-rm/DECSLRM). + mLeftMargin = Math.min(getArg0(1) - 1, mColumns - 2); + mRightMargin = Math.max(mLeftMargin + 1, Math.min(getArg1(mColumns), mColumns)); + // DECSLRM moves the cursorColor to column 1, line 1 of the page. + setCursorPosition(0, 0); + } else { + // Save cursorColor (ANSI.SYS), available only when DECLRMM is disabled. + saveCursor(); + } + break; + case 't': // Window manipulation (from dtterm, as well as extensions) + switch (getArg0(0)) { + case 11: // Report xterm window state. If the xterm window is open (non-iconified), it returns CSI 1 t . + mSession.write("\033[1t"); + break; + case 13: // Report xterm window position. Result is CSI 3 ; x ; y t + mSession.write("\033[3;0;0t"); + break; + case 14: // Report xterm window in pixels. Result is CSI 4 ; height ; width t + // We just report characters time 12 here. + mSession.write(String.format(Locale.US, "\033[4;%d;%dt", mRows * 12, mColumns * 12)); + break; + case 18: // Report the size of the text area in characters. Result is CSI 8 ; height ; width t + mSession.write(String.format(Locale.US, "\033[8;%d;%dt", mRows, mColumns)); + break; + case 19: // Report the size of the screen in characters. Result is CSI 9 ; height ; width t + // We report the same size as the view, since it's the view really isn't resizable from the shell. + mSession.write(String.format(Locale.US, "\033[9;%d;%dt", mRows, mColumns)); + break; + case 20: // Report xterm windows icon label. Result is OSC L label ST. Disabled due to security concerns: + mSession.write("\033]LIconLabel\033\\"); + break; + case 21: // Report xterm windows title. Result is OSC l label ST. Disabled due to security concerns: + mSession.write("\033]l\033\\"); + break; + case 22: + // 22;0 -> Save xterm icon and window title on stack. + // 22;1 -> Save xterm icon title on stack. + // 22;2 -> Save xterm window title on stack. + mTitleStack.push(mTitle); + if (mTitleStack.size() > 20) { + // Limit size + mTitleStack.remove(0); + } + break; + case 23: // Like 22 above but restore from stack. + if (!mTitleStack.isEmpty()) setTitle(mTitleStack.pop()); + break; + default: + // Ignore window manipulation. + break; + } + break; + case 'u': // Restore cursorColor (ANSI.SYS). + restoreCursor(); + break; + case ' ': + continueSequence(ESC_CSI_ARGS_SPACE); + break; + default: + parseArg(b); + break; + } + } + + /** + * Select Graphic Rendition (SGR) - see http://en.wikipedia.org/wiki/ANSI_escape_code#graphics. + */ + private void selectGraphicRendition() { + if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; + for (int i = 0; i <= mArgIndex; i++) { + int code = mArgs[i]; + if (code < 0) { + if (mArgIndex > 0) { + continue; + } else { + code = 0; + } + } + if (code == 0) { // reset + mForeColor = TextStyle.COLOR_INDEX_FOREGROUND; + mBackColor = TextStyle.COLOR_INDEX_BACKGROUND; + mEffect = 0; + } else if (code == 1) { + mEffect |= TextStyle.CHARACTER_ATTRIBUTE_BOLD; + } else if (code == 2) { + mEffect |= TextStyle.CHARACTER_ATTRIBUTE_DIM; + } else if (code == 3) { + mEffect |= TextStyle.CHARACTER_ATTRIBUTE_ITALIC; + } else if (code == 4) { + mEffect |= TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; + } else if (code == 5) { + mEffect |= TextStyle.CHARACTER_ATTRIBUTE_BLINK; + } else if (code == 7) { + mEffect |= TextStyle.CHARACTER_ATTRIBUTE_INVERSE; + } else if (code == 8) { + mEffect |= TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE; + } else if (code == 9) { + mEffect |= TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH; + } else if (code == 10) { + // Exit alt charset (TERM=linux) - ignore. + } else if (code == 11) { + // Enter alt charset (TERM=linux) - ignore. + } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint. + mEffect &= ~(TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_DIM); + } else if (code == 23) { // not italic, but rarely used as such; clears standout with TERM=screen + mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_ITALIC; + } else if (code == 24) { // underline: none + mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE; + } else if (code == 25) { // blink: none + mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_BLINK; + } else if (code == 27) { // image: positive + mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_INVERSE; + } else if (code == 28) { + mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE; + } else if (code == 29) { + mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH; + } else if (code >= 30 && code <= 37) { + mForeColor = code - 30; + } else if (code == 38 || code == 48) { + // Extended set foregroundColor(38)/backgroundColor (48) color. + // This is followed by either "2;$R;$G;$B" to set a 24-bit color or + // "5;$INDEX" to set an indexed color. + if (i + 2 > mArgIndex) continue; + int firstArg = mArgs[i + 1]; + if (firstArg == 2) { + if (i + 4 > mArgIndex) { + Log.w(EmulatorDebug.LOG_TAG, "Too few CSI" + code + ";2 RGB arguments"); + } else { + int red = mArgs[i + 2], green = mArgs[i + 3], blue = mArgs[i + 4]; + if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) { + finishSequenceAndLogError("Invalid RGB: " + red + "," + green + "," + blue); + } else { + int argbColor = 0xff000000 | (red << 16) | (green << 8) | blue; + if (code == 38) { + mForeColor = argbColor; + } else { + mBackColor = argbColor; + } + } + i += 4; // "2;P_r;P_g;P_r" + } + } else if (firstArg == 5) { + int color = mArgs[i + 2]; + i += 2; // "5;P_s" + if (color >= 0 && color < TextStyle.NUM_INDEXED_COLORS) { + if (code == 38) { + mForeColor = color; + } else { + mBackColor = color; + } + } else { + if (LOG_ESCAPE_SEQUENCES) + Log.w(EmulatorDebug.LOG_TAG, "Invalid color index: " + color); + } + } else { + finishSequenceAndLogError("Invalid ISO-8613-3 SGR first argument: " + firstArg); + } + } else if (code == 39) { // Set default foregroundColor color. + mForeColor = TextStyle.COLOR_INDEX_FOREGROUND; + } else if (code >= 40 && code <= 47) { // Set backgroundColor color. + mBackColor = code - 40; + } else if (code == 49) { // Set default backgroundColor color. + mBackColor = TextStyle.COLOR_INDEX_BACKGROUND; + } else if (code >= 90 && code <= 97) { // Bright foregroundColor colors (aixterm codes). + mForeColor = code - 90 + 8; + } else if (code >= 100 && code <= 107) { // Bright backgroundColor color (aixterm codes). + mBackColor = code - 100 + 8; + } else { + if (LOG_ESCAPE_SEQUENCES) + Log.w(EmulatorDebug.LOG_TAG, String.format("SGR unknown code %d", code)); + } + } + } + + private void doOsc(int b) { + switch (b) { + case 7: // Bell. + doOscSetTextParameters("\007"); + break; + case 27: // Escape. + continueSequence(ESC_OSC_ESC); + break; + default: + collectOSCArgs(b); + break; + } + } + + private void doOscEsc(int b) { + switch (b) { + case '\\': + doOscSetTextParameters("\033\\"); + break; + default: + // The ESC character was not followed by a \, so insert the ESC and + // the current character in arg buffer. + collectOSCArgs(27); + collectOSCArgs(b); + continueSequence(ESC_OSC); + break; + } + } + + /** + * An Operating System Controls (OSC) Set Text Parameters. May come here from BEL or ST. + */ + private void doOscSetTextParameters(String bellOrStringTerminator) { + int value = -1; + String textParameter = ""; + // Extract initial $value from initial "$value;..." string. + for (int mOSCArgTokenizerIndex = 0; mOSCArgTokenizerIndex < mOSCOrDeviceControlArgs.length(); mOSCArgTokenizerIndex++) { + char b = mOSCOrDeviceControlArgs.charAt(mOSCArgTokenizerIndex); + if (b == ';') { + textParameter = mOSCOrDeviceControlArgs.substring(mOSCArgTokenizerIndex + 1); + break; + } else if (b >= '0' && b <= '9') { + value = ((value < 0) ? 0 : value * 10) + (b - '0'); + } else { + unknownSequence(b); + return; + } } + switch (value) { + case 0: // Change icon name and window title to T. + case 1: // Change icon name to T. + case 2: // Change window title to T. + setTitle(textParameter); + break; + case 4: + // P s = 4 ; c ; spec → Change Color Number c to the color specified by spec. This can be a name or RGB + // specification as per XParseColor. Any number of c name pairs may be given. The color numbers correspond + // to the ANSI colors 0-7, their bright versions 8-15, and if supported, the remainder of the 88-color or + // 256-color table. + // If a "?" is given rather than a name or RGB specification, xterm replies with a control sequence of the + // same form which can be used to set the corresponding color. Because more than one pair of color number + // and specification can be given in one control sequence, xterm can make more than one reply. + int colorIndex = -1; + int parsingPairStart = -1; + for (int i = 0; ; i++) { + boolean endOfInput = i == textParameter.length(); + char b = endOfInput ? ';' : textParameter.charAt(i); + if (b == ';') { + if (parsingPairStart < 0) { + parsingPairStart = i + 1; + } else { + if (colorIndex < 0 || colorIndex > 255) { + unknownSequence(b); + return; + } else { + mColors.tryParseColor(colorIndex, textParameter.substring(parsingPairStart, i)); + mSession.onColorsChanged(); + colorIndex = -1; + parsingPairStart = -1; + } + } + } else if (parsingPairStart >= 0) { + // We have passed a color index and are now going through color spec. + } else if (parsingPairStart < 0 && (b >= '0' && b <= '9')) { + colorIndex = ((colorIndex < 0) ? 0 : colorIndex * 10) + (b - '0'); + } else { + unknownSequence(b); + return; + } + if (endOfInput) break; + } + break; + case 10: // Set foregroundColor color. + case 11: // Set backgroundColor color. + case 12: // Set cursorColor color. + int specialIndex = TextStyle.COLOR_INDEX_FOREGROUND + (value - 10); + int lastSemiIndex = 0; + for (int charIndex = 0; ; charIndex++) { + boolean endOfInput = charIndex == textParameter.length(); + if (endOfInput || textParameter.charAt(charIndex) == ';') { + try { + String colorSpec = textParameter.substring(lastSemiIndex, charIndex); + if ("?".equals(colorSpec)) { + // Report current color in the same format xterm and gnome-terminal does. + int rgb = mColors.mCurrentColors[specialIndex]; + int r = (65535 * ((rgb & 0x00FF0000) >> 16)) / 255; + int g = (65535 * ((rgb & 0x0000FF00) >> 8)) / 255; + int b = (65535 * ((rgb & 0x000000FF))) / 255; + mSession.write("\033]" + value + ";rgb:" + String.format(Locale.US, "%04x", r) + "/" + String.format(Locale.US, "%04x", g) + "/" + + String.format(Locale.US, "%04x", b) + bellOrStringTerminator); + } else { + mColors.tryParseColor(specialIndex, colorSpec); + mSession.onColorsChanged(); + } + specialIndex++; + if (endOfInput || (specialIndex > TextStyle.COLOR_INDEX_CURSOR) || ++charIndex >= textParameter.length()) + break; + lastSemiIndex = charIndex; + } catch (NumberFormatException e) { + // Ignore. + } + } + } + break; + case 52: // Manipulate Selection Data. Skip the optional first selection parameter(s). + int startIndex = textParameter.indexOf(";") + 1; + try { + String clipboardText = new String(Base64.decode(textParameter.substring(startIndex), 0), StandardCharsets.UTF_8); + mSession.clipboardText(clipboardText); + } catch (Exception e) { + Log.e(EmulatorDebug.LOG_TAG, "OSC Manipulate selection, invalid string '" + textParameter + ""); + } + break; + case 104: + // "104;$c" → Reset Color Number $c. It is reset to the color specified by the corresponding X + // resource. Any number of c parameters may be given. These parameters correspond to the ANSI colors 0-7, + // their bright versions 8-15, and if supported, the remainder of the 88-color or 256-color table. If no + // parameters are given, the entire table will be reset. + if (textParameter.isEmpty()) { + mColors.reset(); + mSession.onColorsChanged(); + } else { + int lastIndex = 0; + for (int charIndex = 0; ; charIndex++) { + boolean endOfInput = charIndex == textParameter.length(); + if (endOfInput || textParameter.charAt(charIndex) == ';') { + try { + int colorToReset = Integer.parseInt(textParameter.substring(lastIndex, charIndex)); + mColors.reset(colorToReset); + mSession.onColorsChanged(); + if (endOfInput) break; + charIndex++; + lastIndex = charIndex; + } catch (NumberFormatException e) { + // Ignore. + } + } + } + } + break; + case 110: // Reset foregroundColor color. + case 111: // Reset backgroundColor color. + case 112: // Reset cursorColor color. + mColors.reset(TextStyle.COLOR_INDEX_FOREGROUND + (value - 110)); + mSession.onColorsChanged(); + break; + case 119: // Reset highlight color. + break; + default: + unknownParameter(value); + break; + } + finishSequence(); + } + + private void blockClear(int sx, int sy, int w) { + blockClear(sx, sy, w, 1); + } + + private void blockClear(int sx, int sy, int w, int h) { + mScreen.blockSet(sx, sy, w, h, ' ', getStyle()); + } + + private long getStyle() { + return TextStyle.encode(mForeColor, mBackColor, mEffect); + } + + /** + * "CSI P_m h" for set or "CSI P_m l" for reset ANSI mode. + */ + private void doSetMode(boolean newValue) { + int modeBit = getArg0(0); + switch (modeBit) { + case 4: // Set="Insert Mode". Reset="Replace Mode". (IRM). + mInsertMode = newValue; + break; + case 20: // Normal Linefeed (LNM). + unknownParameter(modeBit); + // http://www.vt100.net/docs/vt510-rm/LNM + break; + case 34: + // Normal cursorColor visibility - when using TERM=screen, see + // http://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html + break; + default: + unknownParameter(modeBit); + break; + } + } + + /** + * NOTE: The parameters of this function respect the {@link #DECSET_BIT_ORIGIN_MODE}. Use + * {@link #setCursorRowCol(int, int)} for absolute pos. + */ + private void setCursorPosition(int x, int y) { + boolean originMode = isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE); + int effectiveTopMargin = originMode ? mTopMargin : 0; + int effectiveBottomMargin = originMode ? mBottomMargin : mRows; + int effectiveLeftMargin = originMode ? mLeftMargin : 0; + int effectiveRightMargin = originMode ? mRightMargin : mColumns; + int newRow = Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y, effectiveBottomMargin - 1)); + int newCol = Math.max(effectiveLeftMargin, Math.min(effectiveLeftMargin + x, effectiveRightMargin - 1)); + setCursorRowCol(newRow, newCol); + } + + private void scrollDownOneLine() { + mScrollCounter++; + if (mLeftMargin != 0 || mRightMargin != mColumns) { + // Horizontal margin: Do not put anything into scroll history, just non-margin part of screen up. + mScreen.blockCopy(mLeftMargin, mTopMargin + 1, mRightMargin - mLeftMargin, mBottomMargin - mTopMargin - 1, mLeftMargin, mTopMargin); + // .. and blank bottom row between margins: + mScreen.blockSet(mLeftMargin, mBottomMargin - 1, mRightMargin - mLeftMargin, 1, ' ', mEffect); + } else { + mScreen.scrollDownOneLine(mTopMargin, mBottomMargin, getStyle()); + } + } + + /** + * Process the next ASCII character of a parameter. + */ + private void parseArg(int b) { + if (b >= '0' && b <= '9') { + if (mArgIndex < mArgs.length) { + int oldValue = mArgs[mArgIndex]; + int thisDigit = b - '0'; + int value; + if (oldValue >= 0) { + value = oldValue * 10 + thisDigit; + } else { + value = thisDigit; + } + mArgs[mArgIndex] = value; + } + continueSequence(mEscapeState); + } else if (b == ';') { + if (mArgIndex < mArgs.length) { + mArgIndex++; + } + continueSequence(mEscapeState); + } else { + unknownSequence(b); + } + } + + private int getArg0(int defaultValue) { + return getArg(0, defaultValue, true); + } + + private int getArg1(int defaultValue) { + return getArg(1, defaultValue, true); + } + + private int getArg(int index, int defaultValue, boolean treatZeroAsDefault) { + int result = mArgs[index]; + if (result < 0 || (result == 0 && treatZeroAsDefault)) { + result = defaultValue; + } + return result; + } + + private void collectOSCArgs(int b) { + if (mOSCOrDeviceControlArgs.length() < MAX_OSC_STRING_LENGTH) { + mOSCOrDeviceControlArgs.appendCodePoint(b); + continueSequence(mEscapeState); + } else { + unknownSequence(b); + } + } + + private void unimplementedSequence(int b) { + logError("Unimplemented sequence char '" + (char) b + "' (U+" + String.format("%04x", b) + ")"); + finishSequence(); + } + + private void unknownSequence(int b) { + logError("Unknown sequence char '" + (char) b + "' (numeric value=" + b + ")"); + finishSequence(); + } + + private void unknownParameter(int parameter) { + logError("Unknown parameter: " + parameter); + finishSequence(); + } + + private void logError(String errorType) { + if (LOG_ESCAPE_SEQUENCES) { + StringBuilder buf = new StringBuilder(); + buf.append(errorType); + buf.append(", escapeState="); + buf.append(mEscapeState); + boolean firstArg = true; + if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1; + for (int i = 0; i <= mArgIndex; i++) { + int value = mArgs[i]; + if (value >= 0) { + if (firstArg) { + firstArg = false; + buf.append(", args={"); + } else { + buf.append(','); + } + buf.append(value); + } + } + if (!firstArg) buf.append('}'); + finishSequenceAndLogError(buf.toString()); + } + } + + private void finishSequenceAndLogError(String error) { + if (LOG_ESCAPE_SEQUENCES) Log.w(EmulatorDebug.LOG_TAG, error); + finishSequence(); + } + + private void finishSequence() { + mEscapeState = ESC_NONE; + } + + /** + * Send a Unicode code point to the screen. + * + * @param codePoint The code point of the character to display + */ + private void emitCodePoint(int codePoint) { + mLastEmittedCodePoint = codePoint; + if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) { + // http://www.vt100.net/docs/vt102-ug/table5-15.html. + switch (codePoint) { + case '_': + codePoint = ' '; // Blank. + break; + case '`': + codePoint = '◆'; // Diamond. + break; + case '0': + codePoint = '█'; // Solid block; + break; + case 'a': + codePoint = '▒'; // Checker board. + break; + case 'b': + codePoint = '␉'; // Horizontal menu_main. + break; + case 'c': + codePoint = '␌'; // Form feed. + break; + case 'd': + codePoint = '\r'; // Carriage return. + break; + case 'e': + codePoint = '␊'; // Linefeed. + break; + case 'f': + codePoint = '°'; // Degree. + break; + case 'g': + codePoint = '±'; // Plus-minus. + break; + case 'h': + codePoint = '\n'; // Newline. + break; + case 'i': + codePoint = '␋'; // Vertical menu_main. + break; + case 'j': + codePoint = '┘'; // Lower right corner. + break; + case 'k': + codePoint = '┐'; // Upper right corner. + break; + case 'l': + codePoint = '┌'; // Upper left corner. + break; + case 'm': + codePoint = '└'; // Left left corner. + break; + case 'n': + codePoint = '┼'; // Crossing lines. + break; + case 'o': + codePoint = '⎺'; // Horizontal line - scan 1. + break; + case 'p': + codePoint = '⎻'; // Horizontal line - scan 3. + break; + case 'q': + codePoint = '─'; // Horizontal line - scan 5. + break; + case 'r': + codePoint = '⎼'; // Horizontal line - scan 7. + break; + case 's': + codePoint = '⎽'; // Horizontal line - scan 9. + break; + case 't': + codePoint = '├'; // T facing rightwards. + break; + case 'u': + codePoint = '┤'; // T facing leftwards. + break; + case 'v': + codePoint = '┴'; // T facing upwards. + break; + case 'w': + codePoint = '┬'; // T facing downwards. + break; + case 'x': + codePoint = '│'; // Vertical line. + break; + case 'y': + codePoint = '≤'; // Less than or equal to. + break; + case 'z': + codePoint = '≥'; // Greater than or equal to. + break; + case '{': + codePoint = 'π'; // Pi. + break; + case '|': + codePoint = '≠'; // Not equal to. + break; + case '}': + codePoint = '£'; // UK pound. + break; + case '~': + codePoint = '·'; // Centered dot. + break; + } + } + + final boolean autoWrap = isDecsetInternalBitSet(DECSET_BIT_AUTOWRAP); + final int displayWidth = WcWidth.width(codePoint); + final boolean cursorInLastColumn = mCursorCol == mRightMargin - 1; + + if (autoWrap) { + if (cursorInLastColumn && ((mAboutToAutoWrap && displayWidth == 1) || displayWidth == 2)) { + mScreen.setLineWrap(mCursorRow); + mCursorCol = mLeftMargin; + if (mCursorRow + 1 < mBottomMargin) { + mCursorRow++; + } else { + scrollDownOneLine(); + } + } + } else if (cursorInLastColumn && displayWidth == 2) { + // The behaviour when a wide character is output with cursorColor in the last column when + // autowrap is disabled is not obvious - it's ignored here. + return; + } + + if (mInsertMode && displayWidth > 0) { + // Move character to right one space. + int destCol = mCursorCol + displayWidth; + if (destCol < mRightMargin) + mScreen.blockCopy(mCursorCol, mCursorRow, mRightMargin - destCol, 1, destCol, mCursorRow); + } + + int offsetDueToCombiningChar = ((displayWidth <= 0 && mCursorCol > 0 && !mAboutToAutoWrap) ? 1 : 0); + mScreen.setChar(mCursorCol - offsetDueToCombiningChar, mCursorRow, codePoint, getStyle()); + + if (autoWrap && displayWidth > 0) + mAboutToAutoWrap = (mCursorCol == mRightMargin - displayWidth); + + mCursorCol = Math.min(mCursorCol + displayWidth, mRightMargin - 1); + } + + private void setCursorRow(int row) { + mCursorRow = row; + mAboutToAutoWrap = false; + } + + private void setCursorCol(int col) { + mCursorCol = col; + mAboutToAutoWrap = false; + } + + /** + * Set the cursorColor mode, but limit it to margins if {@link #DECSET_BIT_ORIGIN_MODE} is enabled. + */ + private void setCursorColRespectingOriginMode(int col) { + setCursorPosition(col, mCursorRow); + } + + /** + * TODO: Better name, distinguished from {@link #setCursorPosition(int, int)} by not regarding origin mode. + */ + private void setCursorRowCol(int row, int col) { + mCursorRow = Math.max(0, Math.min(row, mRows - 1)); + mCursorCol = Math.max(0, Math.min(col, mColumns - 1)); + mAboutToAutoWrap = false; + } + + public int getScrollCounter() { + return mScrollCounter; + } + + public void clearScrollCounter() { + mScrollCounter = 0; + } + + /** + * Reset terminal state so user can interact with it regardless of present state. + */ + public void reset() { + mCursorStyle = CURSOR_STYLE_BLOCK; + mArgIndex = 0; + mContinueSequence = false; + mEscapeState = ESC_NONE; + mInsertMode = false; + mTopMargin = mLeftMargin = 0; + mBottomMargin = mRows; + mRightMargin = mColumns; + mAboutToAutoWrap = false; + mForeColor = mSavedStateMain.mSavedForeColor = mSavedStateAlt.mSavedForeColor = TextStyle.COLOR_INDEX_FOREGROUND; + mBackColor = mSavedStateMain.mSavedBackColor = mSavedStateAlt.mSavedBackColor = TextStyle.COLOR_INDEX_BACKGROUND; + setDefaultTabStops(); + + mUseLineDrawingG0 = mUseLineDrawingG1 = false; + mUseLineDrawingUsesG0 = true; + + mSavedStateMain.mSavedCursorRow = mSavedStateMain.mSavedCursorCol = mSavedStateMain.mSavedEffect = mSavedStateMain.mSavedDecFlags = 0; + mSavedStateAlt.mSavedCursorRow = mSavedStateAlt.mSavedCursorCol = mSavedStateAlt.mSavedEffect = mSavedStateAlt.mSavedDecFlags = 0; + mCurrentDecSetFlags = 0; + // Initial wrap-around is not accurate but makes terminal more useful, especially on a small screen: + setDecsetinternalBit(DECSET_BIT_AUTOWRAP, true); + setDecsetinternalBit(DECSET_BIT_SHOWING_CURSOR, true); + mSavedDecSetFlags = mSavedStateMain.mSavedDecFlags = mSavedStateAlt.mSavedDecFlags = mCurrentDecSetFlags; + + // XXX: Should we set terminal driver back to IUTF8 with termios? + mUtf8Index = mUtf8ToFollow = 0; + + mColors.reset(); + mSession.onColorsChanged(); + } + + public void setColorScheme(TerminalColorScheme colorScheme) { + mColors.reset(colorScheme); + mSession.onColorsChanged(); + } + + public String getSelectedText(int x1, int y1, int x2, int y2) { + return mScreen.getSelectedText(x1, y1, x2, y2); + } + + /** + * Get the terminal session's title (null if not set). + */ + public String getTitle() { + return mTitle; + } + + /** + * Change the terminal session's title. + */ + private void setTitle(String newTitle) { + String oldTitle = mTitle; + mTitle = newTitle; + if (!Objects.equals(oldTitle, newTitle)) { + mSession.titleChanged(oldTitle, newTitle); + } + } + + /** + * If DECSET 2004 is set, prefix paste with "\033[200~" and suffix with "\033[201~". + */ + public void paste(String text) { + // First: Always remove escape key and C1 control characters [0x80,0x9F]: + text = text.replaceAll("(\u001B|[\u0080-\u009F])", ""); + // Second: Convert DOS (\r\n) CRLF newlines and linefeeds (\n) into carriage returns (\r==13). + text = text.replaceAll("\r?\n", "\r"); + + // Then: Implement bracketed paste mode if enabled: + boolean bracketed = isDecsetInternalBitSet(DECSET_BIT_BRACKETED_PASTE_MODE); + if (bracketed) mSession.write("\033[200~"); + mSession.write(text); + if (bracketed) mSession.write("\033[201~"); + } + + /** + * http://www.vt100.net/docs/vt510-rm/DECSC + */ + static final class SavedScreenState { /** - * If DECSET 2004 is set, prefix paste with "\033[200~" and suffix with "\033[201~". + * Saved state of the cursorColor position, Used to implement the save/restore cursorColor position escape sequences. */ - public void paste(String text) { - // First: Always remove escape key and C1 control characters [0x80,0x9F]: - text = text.replaceAll("(\u001B|[\u0080-\u009F])", ""); - // Second: Convert DOS (\r\n) CRLF newlines and linefeeds (\n) into carriage returns (\r==13). - text = text.replaceAll("\r?\n", "\r"); + int mSavedCursorRow, mSavedCursorCol; + int mSavedEffect, mSavedForeColor, mSavedBackColor; + int mSavedDecFlags; + boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true; + } - // Then: Implement bracketed paste mode if enabled: - boolean bracketed = isDecsetInternalBitSet(DECSET_BIT_BRACKETED_PASTE_MODE); - if (bracketed) mSession.write("\033[200~"); - mSession.write(text); - if (bracketed) mSession.write("\033[201~"); - } - - /** - * http://www.vt100.net/docs/vt510-rm/DECSC - */ - static final class SavedScreenState { - /** - * Saved state of the cursorColor position, Used to implement the save/restore cursorColor position escape sequences. - */ - int mSavedCursorRow, mSavedCursorCol; - int mSavedEffect, mSavedForeColor, mSavedBackColor; - int mSavedDecFlags; - boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true; - } - - @Override - public String toString() { - return "TerminalEmulator[size=" + mScreen.mColumns + "x" + mScreen.mScreenRows + ", margins={" + mTopMargin + "," + mRightMargin + "," + mBottomMargin - + "," + mLeftMargin + "}]"; - } + @Override + public String toString() { + return "TerminalEmulator[size=" + mScreen.mColumns + "x" + mScreen.mScreenRows + ", margins={" + mTopMargin + "," + mRightMargin + "," + mBottomMargin + + "," + mLeftMargin + "}]"; + } } diff --git a/app/src/main/java/io/neoterm/backend/TerminalOutput.java b/app/src/main/java/io/neoterm/backend/TerminalOutput.java index 98900a3..cc21d5d 100755 --- a/app/src/main/java/io/neoterm/backend/TerminalOutput.java +++ b/app/src/main/java/io/neoterm/backend/TerminalOutput.java @@ -2,27 +2,39 @@ package io.neoterm.backend; import java.nio.charset.StandardCharsets; -/** A client which receives callbacks from events triggered by feeding input to a {@link TerminalEmulator}. */ +/** + * A client which receives callbacks from events triggered by feeding input to a {@link TerminalEmulator}. + */ public abstract class TerminalOutput { - /** Write a string using the UTF-8 encoding to the terminal client. */ - public final void write(String data) { - byte[] bytes = data.getBytes(StandardCharsets.UTF_8); - write(bytes, 0, bytes.length); - } + /** + * Write a string using the UTF-8 encoding to the terminal client. + */ + public final void write(String data) { + byte[] bytes = data.getBytes(StandardCharsets.UTF_8); + write(bytes, 0, bytes.length); + } - /** Write bytes to the terminal client. */ - public abstract void write(byte[] data, int offset, int count); + /** + * Write bytes to the terminal client. + */ + public abstract void write(byte[] data, int offset, int count); - /** Notify the terminal client that the terminal title has changed. */ - public abstract void titleChanged(String oldTitle, String newTitle); + /** + * Notify the terminal client that the terminal title has changed. + */ + public abstract void titleChanged(String oldTitle, String newTitle); - /** Notify the terminal client that the terminal title has changed. */ - public abstract void clipboardText(String text); + /** + * Notify the terminal client that the terminal title has changed. + */ + public abstract void clipboardText(String text); - /** Notify the terminal client that a bell character (ASCII 7, bell, BEL, \a, ^G)) has been received. */ - public abstract void onBell(); + /** + * Notify the terminal client that a bell character (ASCII 7, bell, BEL, \a, ^G)) has been received. + */ + public abstract void onBell(); - public abstract void onColorsChanged(); + public abstract void onColorsChanged(); } diff --git a/app/src/main/java/io/neoterm/backend/TerminalRow.java b/app/src/main/java/io/neoterm/backend/TerminalRow.java index 53843cb..567d396 100755 --- a/app/src/main/java/io/neoterm/backend/TerminalRow.java +++ b/app/src/main/java/io/neoterm/backend/TerminalRow.java @@ -9,239 +9,257 @@ import java.util.Arrays; */ public final class TerminalRow { - private static final float SPARE_CAPACITY_FACTOR = 1.5f; + private static final float SPARE_CAPACITY_FACTOR = 1.5f; - /** The number of columns in this terminal row. */ - private final int mColumns; - /** The text filling this terminal row. */ - public char[] mText; - /** The number of java char:s used in {@link #mText}. */ - private short mSpaceUsed; - /** If this row has been line wrapped due to text output at the end of line. */ - boolean mLineWrap; - /** The style bits of each cell in the row. See {@link TextStyle}. */ - final long[] mStyle; - /** If this row might contain chars with width != 1, used for deactivating fast path */ - boolean mHasNonOneWidthOrSurrogateChars; + /** + * The number of columns in this terminal row. + */ + private final int mColumns; + /** + * The text filling this terminal row. + */ + public char[] mText; + /** + * The number of java char:s used in {@link #mText}. + */ + private short mSpaceUsed; + /** + * If this row has been line wrapped due to text output at the end of line. + */ + boolean mLineWrap; + /** + * The style bits of each cell in the row. See {@link TextStyle}. + */ + final long[] mStyle; + /** + * If this row might contain chars with width != 1, used for deactivating fast path + */ + boolean mHasNonOneWidthOrSurrogateChars; - /** Construct a blank row (containing only whitespace, ' ') with a specified style. */ - public TerminalRow(int columns, long style) { - mColumns = columns; - mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)]; - mStyle = new long[columns]; - clear(style); + /** + * Construct a blank row (containing only whitespace, ' ') with a specified style. + */ + public TerminalRow(int columns, long style) { + mColumns = columns; + mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)]; + mStyle = new long[columns]; + clear(style); + } + + /** + * NOTE: The sourceX2 is exclusive. + */ + public void copyInterval(TerminalRow line, int sourceX1, int sourceX2, int destinationX) { + mHasNonOneWidthOrSurrogateChars |= line.mHasNonOneWidthOrSurrogateChars; + final int x1 = line.findStartOfColumn(sourceX1); + final int x2 = line.findStartOfColumn(sourceX2); + boolean startingFromSecondHalfOfWideChar = (sourceX1 > 0 && line.wideDisplayCharacterStartingAt(sourceX1 - 1)); + final char[] sourceChars = (this == line) ? Arrays.copyOf(line.mText, line.mText.length) : line.mText; + int latestNonCombiningWidth = 0; + for (int i = x1; i < x2; i++) { + char sourceChar = sourceChars[i]; + int codePoint = Character.isHighSurrogate(sourceChar) ? Character.toCodePoint(sourceChar, sourceChars[++i]) : sourceChar; + if (startingFromSecondHalfOfWideChar) { + // Just treat copying second half of wide char as copying whitespace. + codePoint = ' '; + startingFromSecondHalfOfWideChar = false; + } + int w = WcWidth.width(codePoint); + if (w > 0) { + destinationX += latestNonCombiningWidth; + sourceX1 += latestNonCombiningWidth; + latestNonCombiningWidth = w; + } + setChar(destinationX, codePoint, line.getStyle(sourceX1)); } + } - /** NOTE: The sourceX2 is exclusive. */ - public void copyInterval(TerminalRow line, int sourceX1, int sourceX2, int destinationX) { - mHasNonOneWidthOrSurrogateChars |= line.mHasNonOneWidthOrSurrogateChars; - final int x1 = line.findStartOfColumn(sourceX1); - final int x2 = line.findStartOfColumn(sourceX2); - boolean startingFromSecondHalfOfWideChar = (sourceX1 > 0 && line.wideDisplayCharacterStartingAt(sourceX1 - 1)); - final char[] sourceChars = (this == line) ? Arrays.copyOf(line.mText, line.mText.length) : line.mText; - int latestNonCombiningWidth = 0; - for (int i = x1; i < x2; i++) { - char sourceChar = sourceChars[i]; - int codePoint = Character.isHighSurrogate(sourceChar) ? Character.toCodePoint(sourceChar, sourceChars[++i]) : sourceChar; - if (startingFromSecondHalfOfWideChar) { - // Just treat copying second half of wide char as copying whitespace. - codePoint = ' '; - startingFromSecondHalfOfWideChar = false; - } - int w = WcWidth.width(codePoint); - if (w > 0) { - destinationX += latestNonCombiningWidth; - sourceX1 += latestNonCombiningWidth; - latestNonCombiningWidth = w; - } - setChar(destinationX, codePoint, line.getStyle(sourceX1)); - } - } + public int getSpaceUsed() { + return mSpaceUsed; + } - public int getSpaceUsed() { - return mSpaceUsed; - } + /** + * Note that the column may end of second half of wide character. + */ + public int findStartOfColumn(int column) { + if (column == mColumns) return getSpaceUsed(); - /** Note that the column may end of second half of wide character. */ - public int findStartOfColumn(int column) { - if (column == mColumns) return getSpaceUsed(); - - int currentColumn = 0; - int currentCharIndex = 0; - while (true) { // 0<2 1 < 2 - int newCharIndex = currentCharIndex; - char c = mText[newCharIndex++]; // cci=1, cci=2 - boolean isHigh = Character.isHighSurrogate(c); - int codePoint = isHigh ? Character.toCodePoint(c, mText[newCharIndex++]) : c; - int wcwidth = WcWidth.width(codePoint); // 1, 2 - if (wcwidth > 0) { - currentColumn += wcwidth; - if (currentColumn == column) { - while (newCharIndex < mSpaceUsed) { - // Skip combining chars. - if (Character.isHighSurrogate(mText[newCharIndex])) { - if (WcWidth.width(Character.toCodePoint(mText[newCharIndex], mText[newCharIndex + 1])) <= 0) { - newCharIndex += 2; - } else { - break; - } - } else if (WcWidth.width(mText[newCharIndex]) <= 0) { - newCharIndex++; - } else { - break; - } - } - return newCharIndex; - } else if (currentColumn > column) { - // Wide column going past end. - return currentCharIndex; - } - } - currentCharIndex = newCharIndex; - } - } - - private boolean wideDisplayCharacterStartingAt(int column) { - for (int currentCharIndex = 0, currentColumn = 0; currentCharIndex < mSpaceUsed; ) { - char c = mText[currentCharIndex++]; - int codePoint = Character.isHighSurrogate(c) ? Character.toCodePoint(c, mText[currentCharIndex++]) : c; - int wcwidth = WcWidth.width(codePoint); - if (wcwidth > 0) { - if (currentColumn == column && wcwidth == 2) return true; - currentColumn += wcwidth; - if (currentColumn > column) return false; - } - } - return false; - } - - public void clear(long style) { - Arrays.fill(mText, ' '); - Arrays.fill(mStyle, style); - mSpaceUsed = (short) mColumns; - mHasNonOneWidthOrSurrogateChars = false; - } - - // https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26 - public void setChar(int columnToSet, int codePoint, long style) { - mStyle[columnToSet] = style; - - final int newCodePointDisplayWidth = WcWidth.width(codePoint); - - // Fast path when we don't have any chars with width != 1 - if (!mHasNonOneWidthOrSurrogateChars) { - if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT || newCodePointDisplayWidth != 1) { - mHasNonOneWidthOrSurrogateChars = true; + int currentColumn = 0; + int currentCharIndex = 0; + while (true) { // 0<2 1 < 2 + int newCharIndex = currentCharIndex; + char c = mText[newCharIndex++]; // cci=1, cci=2 + boolean isHigh = Character.isHighSurrogate(c); + int codePoint = isHigh ? Character.toCodePoint(c, mText[newCharIndex++]) : c; + int wcwidth = WcWidth.width(codePoint); // 1, 2 + if (wcwidth > 0) { + currentColumn += wcwidth; + if (currentColumn == column) { + while (newCharIndex < mSpaceUsed) { + // Skip combining chars. + if (Character.isHighSurrogate(mText[newCharIndex])) { + if (WcWidth.width(Character.toCodePoint(mText[newCharIndex], mText[newCharIndex + 1])) <= 0) { + newCharIndex += 2; + } else { + break; + } + } else if (WcWidth.width(mText[newCharIndex]) <= 0) { + newCharIndex++; } else { - mText[columnToSet] = (char) codePoint; - return; + break; } + } + return newCharIndex; + } else if (currentColumn > column) { + // Wide column going past end. + return currentCharIndex; } + } + currentCharIndex = newCharIndex; + } + } - final boolean newIsCombining = newCodePointDisplayWidth <= 0; + private boolean wideDisplayCharacterStartingAt(int column) { + for (int currentCharIndex = 0, currentColumn = 0; currentCharIndex < mSpaceUsed; ) { + char c = mText[currentCharIndex++]; + int codePoint = Character.isHighSurrogate(c) ? Character.toCodePoint(c, mText[currentCharIndex++]) : c; + int wcwidth = WcWidth.width(codePoint); + if (wcwidth > 0) { + if (currentColumn == column && wcwidth == 2) return true; + currentColumn += wcwidth; + if (currentColumn > column) return false; + } + } + return false; + } - boolean wasExtraColForWideChar = (columnToSet > 0) && wideDisplayCharacterStartingAt(columnToSet - 1); + public void clear(long style) { + Arrays.fill(mText, ' '); + Arrays.fill(mStyle, style); + mSpaceUsed = (short) mColumns; + mHasNonOneWidthOrSurrogateChars = false; + } - if (newIsCombining) { - // When standing at second half of wide character and inserting combining: - if (wasExtraColForWideChar) columnToSet--; - } else { - // Check if we are overwriting the second half of a wide character starting at the previous column: - if (wasExtraColForWideChar) setChar(columnToSet - 1, ' ', style); - // Check if we are overwriting the first half of a wide character starting at the next column: - boolean overwritingWideCharInNextColumn = newCodePointDisplayWidth == 2 && wideDisplayCharacterStartingAt(columnToSet + 1); - if (overwritingWideCharInNextColumn) setChar(columnToSet + 1, ' ', style); - } + // https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26 + public void setChar(int columnToSet, int codePoint, long style) { + mStyle[columnToSet] = style; - char[] text = mText; - final int oldStartOfColumnIndex = findStartOfColumn(columnToSet); - final int oldCodePointDisplayWidth = WcWidth.width(text, oldStartOfColumnIndex); + final int newCodePointDisplayWidth = WcWidth.width(codePoint); - // Get the number of elements in the mText array this column uses now - int oldCharactersUsedForColumn; - if (columnToSet + oldCodePointDisplayWidth < mColumns) { - oldCharactersUsedForColumn = findStartOfColumn(columnToSet + oldCodePointDisplayWidth) - oldStartOfColumnIndex; - } else { - // Last character. - oldCharactersUsedForColumn = mSpaceUsed - oldStartOfColumnIndex; - } - - // Find how many chars this column will need - int newCharactersUsedForColumn = Character.charCount(codePoint); - if (newIsCombining) { - // Combining characters are added to the contents of the column instead of overwriting them, so that they - // modify the existing contents. - // FIXME: Put a limit of combining characters. - // FIXME: Unassigned characters also get width=0. - newCharactersUsedForColumn += oldCharactersUsedForColumn; - } - - int oldNextColumnIndex = oldStartOfColumnIndex + oldCharactersUsedForColumn; - int newNextColumnIndex = oldStartOfColumnIndex + newCharactersUsedForColumn; - - final int javaCharDifference = newCharactersUsedForColumn - oldCharactersUsedForColumn; - if (javaCharDifference > 0) { - // Shift the rest of the line right. - int oldCharactersAfterColumn = mSpaceUsed - oldNextColumnIndex; - if (mSpaceUsed + javaCharDifference > text.length) { - // We need to grow the array - char[] newText = new char[text.length + mColumns]; - System.arraycopy(text, 0, newText, 0, oldStartOfColumnIndex + oldCharactersUsedForColumn); - System.arraycopy(text, oldNextColumnIndex, newText, newNextColumnIndex, oldCharactersAfterColumn); - mText = text = newText; - } else { - System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, oldCharactersAfterColumn); - } - } else if (javaCharDifference < 0) { - // Shift the rest of the line left. - System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - oldNextColumnIndex); - } - mSpaceUsed += javaCharDifference; - - // Store char. A combining character is stored at the end of the existing contents so that it modifies them: - //noinspection ResultOfMethodCallIgnored - since we already now how many java chars is used. - Character.toChars(codePoint, text, oldStartOfColumnIndex + (newIsCombining ? oldCharactersUsedForColumn : 0)); - - if (oldCodePointDisplayWidth == 2 && newCodePointDisplayWidth == 1) { - // Replace second half of wide char with a space. Which mean that we actually add a ' ' java character. - if (mSpaceUsed + 1 > text.length) { - char[] newText = new char[text.length + mColumns]; - System.arraycopy(text, 0, newText, 0, newNextColumnIndex); - System.arraycopy(text, newNextColumnIndex, newText, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex); - mText = text = newText; - } else { - System.arraycopy(text, newNextColumnIndex, text, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex); - } - text[newNextColumnIndex] = ' '; - - ++mSpaceUsed; - } else if (oldCodePointDisplayWidth == 1 && newCodePointDisplayWidth == 2) { - if (columnToSet == mColumns - 1) { - throw new IllegalArgumentException("Cannot put wide character in last column"); - } else if (columnToSet == mColumns - 2) { - // Truncate the line to the second part of this wide char: - mSpaceUsed = (short) newNextColumnIndex; - } else { - // Overwrite the contents of the next column, which mean we actually remove java characters. Due to the - // check at the beginning of this method we know that we are not overwriting a wide char. - int newNextNextColumnIndex = newNextColumnIndex + (Character.isHighSurrogate(mText[newNextColumnIndex]) ? 2 : 1); - int nextLen = newNextNextColumnIndex - newNextColumnIndex; - - // Shift the array leftwards. - System.arraycopy(text, newNextNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - newNextNextColumnIndex); - mSpaceUsed -= nextLen; - } - } + // Fast path when we don't have any chars with width != 1 + if (!mHasNonOneWidthOrSurrogateChars) { + if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT || newCodePointDisplayWidth != 1) { + mHasNonOneWidthOrSurrogateChars = true; + } else { + mText[columnToSet] = (char) codePoint; + return; + } } - boolean isBlank() { - for (int charIndex = 0, charLen = getSpaceUsed(); charIndex < charLen; charIndex++) - if (mText[charIndex] != ' ') return false; - return true; + final boolean newIsCombining = newCodePointDisplayWidth <= 0; + + boolean wasExtraColForWideChar = (columnToSet > 0) && wideDisplayCharacterStartingAt(columnToSet - 1); + + if (newIsCombining) { + // When standing at second half of wide character and inserting combining: + if (wasExtraColForWideChar) columnToSet--; + } else { + // Check if we are overwriting the second half of a wide character starting at the previous column: + if (wasExtraColForWideChar) setChar(columnToSet - 1, ' ', style); + // Check if we are overwriting the first half of a wide character starting at the next column: + boolean overwritingWideCharInNextColumn = newCodePointDisplayWidth == 2 && wideDisplayCharacterStartingAt(columnToSet + 1); + if (overwritingWideCharInNextColumn) setChar(columnToSet + 1, ' ', style); } - public final long getStyle(int column) { - return mStyle[column]; + char[] text = mText; + final int oldStartOfColumnIndex = findStartOfColumn(columnToSet); + final int oldCodePointDisplayWidth = WcWidth.width(text, oldStartOfColumnIndex); + + // Get the number of elements in the mText array this column uses now + int oldCharactersUsedForColumn; + if (columnToSet + oldCodePointDisplayWidth < mColumns) { + oldCharactersUsedForColumn = findStartOfColumn(columnToSet + oldCodePointDisplayWidth) - oldStartOfColumnIndex; + } else { + // Last character. + oldCharactersUsedForColumn = mSpaceUsed - oldStartOfColumnIndex; } + // Find how many chars this column will need + int newCharactersUsedForColumn = Character.charCount(codePoint); + if (newIsCombining) { + // Combining characters are added to the contents of the column instead of overwriting them, so that they + // modify the existing contents. + // FIXME: Put a limit of combining characters. + // FIXME: Unassigned characters also get width=0. + newCharactersUsedForColumn += oldCharactersUsedForColumn; + } + + int oldNextColumnIndex = oldStartOfColumnIndex + oldCharactersUsedForColumn; + int newNextColumnIndex = oldStartOfColumnIndex + newCharactersUsedForColumn; + + final int javaCharDifference = newCharactersUsedForColumn - oldCharactersUsedForColumn; + if (javaCharDifference > 0) { + // Shift the rest of the line right. + int oldCharactersAfterColumn = mSpaceUsed - oldNextColumnIndex; + if (mSpaceUsed + javaCharDifference > text.length) { + // We need to grow the array + char[] newText = new char[text.length + mColumns]; + System.arraycopy(text, 0, newText, 0, oldStartOfColumnIndex + oldCharactersUsedForColumn); + System.arraycopy(text, oldNextColumnIndex, newText, newNextColumnIndex, oldCharactersAfterColumn); + mText = text = newText; + } else { + System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, oldCharactersAfterColumn); + } + } else if (javaCharDifference < 0) { + // Shift the rest of the line left. + System.arraycopy(text, oldNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - oldNextColumnIndex); + } + mSpaceUsed += javaCharDifference; + + // Store char. A combining character is stored at the end of the existing contents so that it modifies them: + //noinspection ResultOfMethodCallIgnored - since we already now how many java chars is used. + Character.toChars(codePoint, text, oldStartOfColumnIndex + (newIsCombining ? oldCharactersUsedForColumn : 0)); + + if (oldCodePointDisplayWidth == 2 && newCodePointDisplayWidth == 1) { + // Replace second half of wide char with a space. Which mean that we actually add a ' ' java character. + if (mSpaceUsed + 1 > text.length) { + char[] newText = new char[text.length + mColumns]; + System.arraycopy(text, 0, newText, 0, newNextColumnIndex); + System.arraycopy(text, newNextColumnIndex, newText, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex); + mText = text = newText; + } else { + System.arraycopy(text, newNextColumnIndex, text, newNextColumnIndex + 1, mSpaceUsed - newNextColumnIndex); + } + text[newNextColumnIndex] = ' '; + + ++mSpaceUsed; + } else if (oldCodePointDisplayWidth == 1 && newCodePointDisplayWidth == 2) { + if (columnToSet == mColumns - 1) { + throw new IllegalArgumentException("Cannot put wide character in last column"); + } else if (columnToSet == mColumns - 2) { + // Truncate the line to the second part of this wide char: + mSpaceUsed = (short) newNextColumnIndex; + } else { + // Overwrite the contents of the next column, which mean we actually remove java characters. Due to the + // check at the beginning of this method we know that we are not overwriting a wide char. + int newNextNextColumnIndex = newNextColumnIndex + (Character.isHighSurrogate(mText[newNextColumnIndex]) ? 2 : 1); + int nextLen = newNextNextColumnIndex - newNextColumnIndex; + + // Shift the array leftwards. + System.arraycopy(text, newNextNextColumnIndex, text, newNextColumnIndex, mSpaceUsed - newNextNextColumnIndex); + mSpaceUsed -= nextLen; + } + } + } + + boolean isBlank() { + for (int charIndex = 0, charLen = getSpaceUsed(); charIndex < charLen; charIndex++) + if (mText[charIndex] != ' ') return false; + return true; + } + + public final long getStyle(int column) { + return mStyle[column]; + } + } diff --git a/app/src/main/java/io/neoterm/backend/TerminalSession.java b/app/src/main/java/io/neoterm/backend/TerminalSession.java index 5e82bba..89b1ed4 100755 --- a/app/src/main/java/io/neoterm/backend/TerminalSession.java +++ b/app/src/main/java/io/neoterm/backend/TerminalSession.java @@ -8,11 +8,7 @@ import android.system.Os; import android.system.OsConstants; import android.util.Log; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.util.UUID; @@ -30,322 +26,352 @@ import java.util.UUID; */ public class TerminalSession extends TerminalOutput { - /** Callback to be invoked when a {@link TerminalSession} changes. */ - public interface SessionChangedCallback { - void onTextChanged(TerminalSession changedSession); + /** + * Callback to be invoked when a {@link TerminalSession} changes. + */ + public interface SessionChangedCallback { + void onTextChanged(TerminalSession changedSession); - void onTitleChanged(TerminalSession changedSession); + void onTitleChanged(TerminalSession changedSession); - void onSessionFinished(TerminalSession finishedSession); + void onSessionFinished(TerminalSession finishedSession); - void onClipboardText(TerminalSession session, String text); + void onClipboardText(TerminalSession session, String text); - void onBell(TerminalSession session); + void onBell(TerminalSession session); - void onColorsChanged(TerminalSession session); + void onColorsChanged(TerminalSession session); + } + + @SuppressWarnings("JavaReflectionMemberAccess") + private static FileDescriptor wrapFileDescriptor(int fileDescriptor) { + FileDescriptor result = new FileDescriptor(); + try { + Field descriptorField; + try { + descriptorField = FileDescriptor.class.getDeclaredField("descriptor"); + } catch (NoSuchFieldException e) { + // For desktop java: + descriptorField = FileDescriptor.class.getDeclaredField("fd"); + } + descriptorField.setAccessible(true); + descriptorField.set(result, fileDescriptor); + } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) { + Log.wtf(EmulatorDebug.LOG_TAG, "Error accessing FileDescriptor#descriptor private field", e); + System.exit(1); } + return result; + } - @SuppressWarnings("JavaReflectionMemberAccess") - private static FileDescriptor wrapFileDescriptor(int fileDescriptor) { - FileDescriptor result = new FileDescriptor(); - try { - Field descriptorField; - try { - descriptorField = FileDescriptor.class.getDeclaredField("descriptor"); - } catch (NoSuchFieldException e) { - // For desktop java: - descriptorField = FileDescriptor.class.getDeclaredField("fd"); - } - descriptorField.setAccessible(true); - descriptorField.set(result, fileDescriptor); - } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) { - Log.wtf(EmulatorDebug.LOG_TAG, "Error accessing FileDescriptor#descriptor private field", e); - System.exit(1); - } - return result; - } + private static final int MSG_NEW_INPUT = 1; + private static final int MSG_PROCESS_EXITED = 4; - private static final int MSG_NEW_INPUT = 1; - private static final int MSG_PROCESS_EXITED = 4; + public final String mHandle = UUID.randomUUID().toString(); - public final String mHandle = UUID.randomUUID().toString(); + private TerminalEmulator mEmulator; - private TerminalEmulator mEmulator; + /** + * A queue written to from a separate thread when the process outputs, and read by main thread to process by + * terminal emulator. + */ + private final ByteQueue mProcessToTerminalIOQueue = new ByteQueue(4096); + /** + * A queue written to from the main thread due to user interaction, and read by another thread which forwards by + * writing to the {@link #mTerminalFileDescriptor}. + */ + private final ByteQueue mTerminalToProcessIOQueue = new ByteQueue(4096); + /** + * Buffer to write translate code points into utf8 before writing to mTerminalToProcessIOQueue + */ + private final byte[] mUtf8InputBuffer = new byte[5]; - /** - * A queue written to from a separate thread when the process outputs, and read by main thread to process by - * terminal emulator. - */ - private final ByteQueue mProcessToTerminalIOQueue = new ByteQueue(4096); - /** - * A queue written to from the main thread due to user interaction, and read by another thread which forwards by - * writing to the {@link #mTerminalFileDescriptor}. - */ - private final ByteQueue mTerminalToProcessIOQueue = new ByteQueue(4096); - /** Buffer to write translate code points into utf8 before writing to mTerminalToProcessIOQueue */ - private final byte[] mUtf8InputBuffer = new byte[5]; + public SessionChangedCallback getSessionChangedCallback() { + return mChangeCallback; + } - public SessionChangedCallback getSessionChangedCallback() { - return mChangeCallback; - } + /** + * Callback which gets notified when a session finishes or changes title. + */ + private final SessionChangedCallback mChangeCallback; - /** Callback which gets notified when a session finishes or changes title. */ - private final SessionChangedCallback mChangeCallback; + /** + * The pid of the executablePath process. 0 if not started and -1 if finished running. + */ + private int mShellPid; - /** The pid of the executablePath process. 0 if not started and -1 if finished running. */ - private int mShellPid; + /** + * The exit status of the executablePath process. Only valid if ${@link #mShellPid} is -1. + */ + private int mShellExitStatus; - /** The exit status of the executablePath process. Only valid if ${@link #mShellPid} is -1. */ - private int mShellExitStatus; + /** + * The file descriptor referencing the master half of a pseudo-terminal pair, resulting from calling + * {@link JNI#createSubprocess(String, String, String[], String[], int[], int, int)}. + */ + private int mTerminalFileDescriptor; - /** - * The file descriptor referencing the master half of a pseudo-terminal pair, resulting from calling - * {@link JNI#createSubprocess(String, String, String[], String[], int[], int, int)}. - */ - private int mTerminalFileDescriptor; + /** + * Set by the application for user identification of session, not by terminal. + */ + public String mSessionName; - /** Set by the application for user identification of session, not by terminal. */ - public String mSessionName; + @SuppressLint("HandlerLeak") + private final Handler mMainThreadHandler = new Handler() { + final byte[] mReceiveBuffer = new byte[4 * 1024]; - @SuppressLint("HandlerLeak") - private final Handler mMainThreadHandler = new Handler() { - final byte[] mReceiveBuffer = new byte[4 * 1024]; - - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_NEW_INPUT && isRunning()) { - int bytesRead = mProcessToTerminalIOQueue.read(mReceiveBuffer, false); - if (bytesRead > 0) { - mEmulator.append(mReceiveBuffer, bytesRead); - notifyScreenUpdate(); - } - } else if (msg.what == MSG_PROCESS_EXITED) { - int exitCode = (Integer) msg.obj; - cleanupResources(exitCode); - mChangeCallback.onSessionFinished(TerminalSession.this); - - String exitDescription = getExitDescription(exitCode); - byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8); - mEmulator.append(bytesToWrite, bytesToWrite.length); - notifyScreenUpdate(); - } - } - }; - - private final String mShellPath; - private final String mCwd; - private final String[] mArgs; - private final String[] mEnv; - - public TerminalSession(String shellPath, String cwd, String[] args, String[] env, SessionChangedCallback changeCallback) { - mChangeCallback = changeCallback; - - this.mShellPath = shellPath; - this.mCwd = cwd; - this.mArgs = args; - this.mEnv = env; - } - - /** Inform the attached pty of the new size and reflow or initialize the emulator. */ - public void updateSize(int columns, int rows) { - if (mEmulator == null) { - initializeEmulator(columns, rows); - } else { - JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns); - mEmulator.resize(columns, rows); - } - } - - /** The terminal title as set through escape sequences or null if none set. */ - public String getTitle() { - return (mEmulator == null) ? null : mEmulator.getTitle(); - } - - /** - * Set the terminal emulator's window size and start terminal emulation. - * - * @param columns The number of columns in the terminal window. - * @param rows The number of rows in the terminal window. - */ - public void initializeEmulator(int columns, int rows) { - mEmulator = new TerminalEmulator(this, columns, rows, /* transcript= */2000); - - int[] processId = new int[1]; - mTerminalFileDescriptor = JNI.createSubprocess(mShellPath, mCwd, mArgs, mEnv, processId, rows, columns); - mShellPid = processId[0]; - - final FileDescriptor terminalFileDescriptorWrapped = wrapFileDescriptor(mTerminalFileDescriptor); - - new Thread("TermSessionInputReader[pid=" + mShellPid + "]") { - @Override - public void run() { - try (InputStream termIn = new FileInputStream(terminalFileDescriptorWrapped)) { - final byte[] buffer = new byte[4096]; - while (true) { - int read = termIn.read(buffer); - if (read == -1) return; - if (!mProcessToTerminalIOQueue.write(buffer, 0, read)) return; - mMainThreadHandler.sendEmptyMessage(MSG_NEW_INPUT); - } - } catch (Exception e) { - // Ignore, just shutting down. - } - } - }.start(); - - new Thread("TermSessionOutputWriter[pid=" + mShellPid + "]") { - @Override - public void run() { - final byte[] buffer = new byte[4096]; - try (FileOutputStream termOut = new FileOutputStream(terminalFileDescriptorWrapped)) { - while (true) { - int bytesToWrite = mTerminalToProcessIOQueue.read(buffer, true); - if (bytesToWrite == -1) return; - termOut.write(buffer, 0, bytesToWrite); - } - } catch (IOException e) { - // Ignore. - } - } - }.start(); - - new Thread("TermSessionWaiter[pid=" + mShellPid + "]") { - @Override - public void run() { - int processExitCode = JNI.waitFor(mShellPid); - mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(MSG_PROCESS_EXITED, processExitCode)); - } - }.start(); - } - - /** Write data to the executablePath process. */ @Override - public void write(byte[] data, int offset, int count) { - if (mShellPid > 0) mTerminalToProcessIOQueue.write(data, offset, count); - } - - /** Write the Unicode code point to the terminal encoded in UTF-8. */ - public void writeCodePoint(boolean prependEscape, int codePoint) { - if (codePoint > 1114111 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) { - // 1114111 (= 2**16 + 1024**2 - 1) is the highest code point, [0xD800,0xDFFF] is the surrogate range. - throw new IllegalArgumentException("Invalid code point: " + codePoint); + public void handleMessage(Message msg) { + if (msg.what == MSG_NEW_INPUT && isRunning()) { + int bytesRead = mProcessToTerminalIOQueue.read(mReceiveBuffer, false); + if (bytesRead > 0) { + mEmulator.append(mReceiveBuffer, bytesRead); + notifyScreenUpdate(); } + } else if (msg.what == MSG_PROCESS_EXITED) { + int exitCode = (Integer) msg.obj; + cleanupResources(exitCode); + mChangeCallback.onSessionFinished(TerminalSession.this); - int bufferPosition = 0; - if (prependEscape) mUtf8InputBuffer[bufferPosition++] = 27; - - if (codePoint <= /* 7 bits */0b1111111) { - mUtf8InputBuffer[bufferPosition++] = (byte) codePoint; - } else if (codePoint <= /* 11 bits */0b11111111111) { - /* 110xxxxx leading byte with leading 5 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b11000000 | (codePoint >> 6)); - /* 10xxxxxx continuation byte with following 6 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111)); - } else if (codePoint <= /* 16 bits */0b1111111111111111) { - /* 1110xxxx leading byte with leading 4 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b11100000 | (codePoint >> 12)); - /* 10xxxxxx continuation byte with following 6 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111)); - /* 10xxxxxx continuation byte with following 6 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111)); - } else { /* We have checked codePoint <= 1114111 above, so we have max 21 bits = 0b111111111111111111111 */ - /* 11110xxx leading byte with leading 3 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b11110000 | (codePoint >> 18)); - /* 10xxxxxx continuation byte with following 6 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 12) & 0b111111)); - /* 10xxxxxx continuation byte with following 6 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111)); - /* 10xxxxxx continuation byte with following 6 bits */ - mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111)); - } - write(mUtf8InputBuffer, 0, bufferPosition); - } - - public TerminalEmulator getEmulator() { - return mEmulator; - } - - /** Notify the {@link #mChangeCallback} that the screen has changed. */ - private void notifyScreenUpdate() { - mChangeCallback.onTextChanged(this); - } - - /** Reset state for terminal emulator state. */ - public void reset() { - mEmulator.reset(); + String exitDescription = getExitDescription(exitCode); + byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8); + mEmulator.append(bytesToWrite, bytesToWrite.length); notifyScreenUpdate(); + } } + }; - /** Finish this terminal session by sending SIGKILL to the executablePath. */ - public void finishIfRunning() { - if (isRunning()) { - try { - Os.kill(mShellPid, OsConstants.SIGKILL); - } catch (ErrnoException e) { - Log.w("neoterm-shell-session", - "Failed sending SIGKILL: " + e.getMessage()); - } + private final String mShellPath; + private final String mCwd; + private final String[] mArgs; + private final String[] mEnv; + + public TerminalSession(String shellPath, String cwd, String[] args, String[] env, SessionChangedCallback changeCallback) { + mChangeCallback = changeCallback; + + this.mShellPath = shellPath; + this.mCwd = cwd; + this.mArgs = args; + this.mEnv = env; + } + + /** + * Inform the attached pty of the new size and reflow or initialize the emulator. + */ + public void updateSize(int columns, int rows) { + if (mEmulator == null) { + initializeEmulator(columns, rows); + } else { + JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns); + mEmulator.resize(columns, rows); + } + } + + /** + * The terminal title as set through escape sequences or null if none set. + */ + public String getTitle() { + return (mEmulator == null) ? null : mEmulator.getTitle(); + } + + /** + * Set the terminal emulator's window size and start terminal emulation. + * + * @param columns The number of columns in the terminal window. + * @param rows The number of rows in the terminal window. + */ + public void initializeEmulator(int columns, int rows) { + mEmulator = new TerminalEmulator(this, columns, rows, /* transcript= */2000); + + int[] processId = new int[1]; + mTerminalFileDescriptor = JNI.createSubprocess(mShellPath, mCwd, mArgs, mEnv, processId, rows, columns); + mShellPid = processId[0]; + + final FileDescriptor terminalFileDescriptorWrapped = wrapFileDescriptor(mTerminalFileDescriptor); + + new Thread("TermSessionInputReader[pid=" + mShellPid + "]") { + @Override + public void run() { + try (InputStream termIn = new FileInputStream(terminalFileDescriptorWrapped)) { + final byte[] buffer = new byte[4096]; + while (true) { + int read = termIn.read(buffer); + if (read == -1) return; + if (!mProcessToTerminalIOQueue.write(buffer, 0, read)) return; + mMainThreadHandler.sendEmptyMessage(MSG_NEW_INPUT); + } + } catch (Exception e) { + // Ignore, just shutting down. } - } + } + }.start(); - protected String getExitDescription(int exitCode) { - String exitDescription = "\r\n[Process completed"; - if (exitCode > 0) { - // Non-zero process exit. - exitDescription += " (code " + exitCode + ")"; - } else if (exitCode < 0) { - // Negated signal. - exitDescription += " (signal " + (-exitCode) + ")"; + new Thread("TermSessionOutputWriter[pid=" + mShellPid + "]") { + @Override + public void run() { + final byte[] buffer = new byte[4096]; + try (FileOutputStream termOut = new FileOutputStream(terminalFileDescriptorWrapped)) { + while (true) { + int bytesToWrite = mTerminalToProcessIOQueue.read(buffer, true); + if (bytesToWrite == -1) return; + termOut.write(buffer, 0, bytesToWrite); + } + } catch (IOException e) { + // Ignore. } - exitDescription += " - press Enter]"; - return exitDescription; + } + }.start(); + + new Thread("TermSessionWaiter[pid=" + mShellPid + "]") { + @Override + public void run() { + int processExitCode = JNI.waitFor(mShellPid); + mMainThreadHandler.sendMessage(mMainThreadHandler.obtainMessage(MSG_PROCESS_EXITED, processExitCode)); + } + }.start(); + } + + /** + * Write data to the executablePath process. + */ + @Override + public void write(byte[] data, int offset, int count) { + if (mShellPid > 0) mTerminalToProcessIOQueue.write(data, offset, count); + } + + /** + * Write the Unicode code point to the terminal encoded in UTF-8. + */ + public void writeCodePoint(boolean prependEscape, int codePoint) { + if (codePoint > 1114111 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) { + // 1114111 (= 2**16 + 1024**2 - 1) is the highest code point, [0xD800,0xDFFF] is the surrogate range. + throw new IllegalArgumentException("Invalid code point: " + codePoint); } - /** Cleanup resources when the process exits. */ - private void cleanupResources(int exitStatus) { - synchronized (this) { - mShellPid = -1; - mShellExitStatus = exitStatus; - } + int bufferPosition = 0; + if (prependEscape) mUtf8InputBuffer[bufferPosition++] = 27; - // Stop the reader and writer threads, and close the I/O streams - mTerminalToProcessIOQueue.close(); - mProcessToTerminalIOQueue.close(); - JNI.close(mTerminalFileDescriptor); + if (codePoint <= /* 7 bits */0b1111111) { + mUtf8InputBuffer[bufferPosition++] = (byte) codePoint; + } else if (codePoint <= /* 11 bits */0b11111111111) { + /* 110xxxxx leading byte with leading 5 bits */ + mUtf8InputBuffer[bufferPosition++] = (byte) (0b11000000 | (codePoint >> 6)); + /* 10xxxxxx continuation byte with following 6 bits */ + mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111)); + } else if (codePoint <= /* 16 bits */0b1111111111111111) { + /* 1110xxxx leading byte with leading 4 bits */ + mUtf8InputBuffer[bufferPosition++] = (byte) (0b11100000 | (codePoint >> 12)); + /* 10xxxxxx continuation byte with following 6 bits */ + mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111)); + /* 10xxxxxx continuation byte with following 6 bits */ + mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111)); + } else { /* We have checked codePoint <= 1114111 above, so we have max 21 bits = 0b111111111111111111111 */ + /* 11110xxx leading byte with leading 3 bits */ + mUtf8InputBuffer[bufferPosition++] = (byte) (0b11110000 | (codePoint >> 18)); + /* 10xxxxxx continuation byte with following 6 bits */ + mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 12) & 0b111111)); + /* 10xxxxxx continuation byte with following 6 bits */ + mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | ((codePoint >> 6) & 0b111111)); + /* 10xxxxxx continuation byte with following 6 bits */ + mUtf8InputBuffer[bufferPosition++] = (byte) (0b10000000 | (codePoint & 0b111111)); + } + write(mUtf8InputBuffer, 0, bufferPosition); + } + + public TerminalEmulator getEmulator() { + return mEmulator; + } + + /** + * Notify the {@link #mChangeCallback} that the screen has changed. + */ + private void notifyScreenUpdate() { + mChangeCallback.onTextChanged(this); + } + + /** + * Reset state for terminal emulator state. + */ + public void reset() { + mEmulator.reset(); + notifyScreenUpdate(); + } + + /** + * Finish this terminal session by sending SIGKILL to the executablePath. + */ + public void finishIfRunning() { + if (isRunning()) { + try { + Os.kill(mShellPid, OsConstants.SIGKILL); + } catch (ErrnoException e) { + Log.w("neoterm-shell-session", + "Failed sending SIGKILL: " + e.getMessage()); + } + } + } + + protected String getExitDescription(int exitCode) { + String exitDescription = "\r\n[Process completed"; + if (exitCode > 0) { + // Non-zero process exit. + exitDescription += " (code " + exitCode + ")"; + } else if (exitCode < 0) { + // Negated signal. + exitDescription += " (signal " + (-exitCode) + ")"; + } + exitDescription += " - press Enter]"; + return exitDescription; + } + + /** + * Cleanup resources when the process exits. + */ + private void cleanupResources(int exitStatus) { + synchronized (this) { + mShellPid = -1; + mShellExitStatus = exitStatus; } - @Override - public void titleChanged(String oldTitle, String newTitle) { - mChangeCallback.onTitleChanged(this); - } + // Stop the reader and writer threads, and close the I/O streams + mTerminalToProcessIOQueue.close(); + mProcessToTerminalIOQueue.close(); + JNI.close(mTerminalFileDescriptor); + } - public synchronized boolean isRunning() { - return mShellPid != -1; - } + @Override + public void titleChanged(String oldTitle, String newTitle) { + mChangeCallback.onTitleChanged(this); + } - /** Only valid if not {@link #isRunning()}. */ - public synchronized int getExitStatus() { - return mShellExitStatus; - } + public synchronized boolean isRunning() { + return mShellPid != -1; + } - @Override - public void clipboardText(String text) { - mChangeCallback.onClipboardText(this, text); - } + /** + * Only valid if not {@link #isRunning()}. + */ + public synchronized int getExitStatus() { + return mShellExitStatus; + } - @Override - public void onBell() { - mChangeCallback.onBell(this); - } + @Override + public void clipboardText(String text) { + mChangeCallback.onClipboardText(this, text); + } - @Override - public void onColorsChanged() { - mChangeCallback.onColorsChanged(this); - } + @Override + public void onBell() { + mChangeCallback.onBell(this); + } - public int getPid() { - return mShellPid; - } + @Override + public void onColorsChanged() { + mChangeCallback.onColorsChanged(this); + } + + public int getPid() { + return mShellPid; + } } diff --git a/app/src/main/java/io/neoterm/backend/TextStyle.java b/app/src/main/java/io/neoterm/backend/TextStyle.java index e28777a..a98c79e 100755 --- a/app/src/main/java/io/neoterm/backend/TextStyle.java +++ b/app/src/main/java/io/neoterm/backend/TextStyle.java @@ -14,77 +14,87 @@ package io.neoterm.backend; */ public final class TextStyle { - public final static int CHARACTER_ATTRIBUTE_BOLD = 1; - public final static int CHARACTER_ATTRIBUTE_ITALIC = 1 << 1; - public final static int CHARACTER_ATTRIBUTE_UNDERLINE = 1 << 2; - public final static int CHARACTER_ATTRIBUTE_BLINK = 1 << 3; - public final static int CHARACTER_ATTRIBUTE_INVERSE = 1 << 4; - public final static int CHARACTER_ATTRIBUTE_INVISIBLE = 1 << 5; - public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6; - /** - * The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable. - *

- * This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that - * come after it as erasable from the screen. - *

- */ - public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7; - /** Dim colors. Also known as faint or half intensity. */ - public final static int CHARACTER_ATTRIBUTE_DIM = 1 << 8; - /** If true (24-bit) color is used for the cell for foregroundColor. */ - private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND = 1 << 9; - /** If true (24-bit) color is used for the cell for foregroundColor. */ - private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND= 1 << 10; + public final static int CHARACTER_ATTRIBUTE_BOLD = 1; + public final static int CHARACTER_ATTRIBUTE_ITALIC = 1 << 1; + public final static int CHARACTER_ATTRIBUTE_UNDERLINE = 1 << 2; + public final static int CHARACTER_ATTRIBUTE_BLINK = 1 << 3; + public final static int CHARACTER_ATTRIBUTE_INVERSE = 1 << 4; + public final static int CHARACTER_ATTRIBUTE_INVISIBLE = 1 << 5; + public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6; + /** + * The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable. + *

+ * This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that + * come after it as erasable from the screen. + *

+ */ + public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7; + /** + * Dim colors. Also known as faint or half intensity. + */ + public final static int CHARACTER_ATTRIBUTE_DIM = 1 << 8; + /** + * If true (24-bit) color is used for the cell for foregroundColor. + */ + private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND = 1 << 9; + /** + * If true (24-bit) color is used for the cell for foregroundColor. + */ + private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND = 1 << 10; - public final static int COLOR_INDEX_FOREGROUND = 256; - public final static int COLOR_INDEX_BACKGROUND = 257; - public final static int COLOR_INDEX_CURSOR = 258; + public final static int COLOR_INDEX_FOREGROUND = 256; + public final static int COLOR_INDEX_BACKGROUND = 257; + public final static int COLOR_INDEX_CURSOR = 258; - /** The 256 standard color entries and the three special (foregroundColor, backgroundColor and cursorColor) ones. */ - public final static int NUM_INDEXED_COLORS = 259; + /** + * The 256 standard color entries and the three special (foregroundColor, backgroundColor and cursorColor) ones. + */ + public final static int NUM_INDEXED_COLORS = 259; - /** Normal foregroundColor and backgroundColor colors and no effects. */ - final static long NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0); + /** + * Normal foregroundColor and backgroundColor colors and no effects. + */ + final static long NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0); - static long encode(int foreColor, int backColor, int effect) { - long result = effect & 0b111111111; - if ((0xff000000 & foreColor) == 0xff000000) { - // 24-bit color. - result |= CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND | ((foreColor & 0x00ffffffL) << 40L); - } else { - // Indexed color. - result |= (foreColor & 0b111111111L) << 40; - } - if ((0xff000000 & backColor) == 0xff000000) { - // 24-bit color. - result |= CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND | ((backColor & 0x00ffffffL) << 16L); - } else { - // Indexed color. - result |= (backColor & 0b111111111L) << 16L; - } - - return result; + static long encode(int foreColor, int backColor, int effect) { + long result = effect & 0b111111111; + if ((0xff000000 & foreColor) == 0xff000000) { + // 24-bit color. + result |= CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND | ((foreColor & 0x00ffffffL) << 40L); + } else { + // Indexed color. + result |= (foreColor & 0b111111111L) << 40; + } + if ((0xff000000 & backColor) == 0xff000000) { + // 24-bit color. + result |= CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND | ((backColor & 0x00ffffffL) << 16L); + } else { + // Indexed color. + result |= (backColor & 0b111111111L) << 16L; } - public static int decodeForeColor(long style) { - if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND) == 0) { - return (int) ((style >>> 40) & 0b111111111L); - } else { - return 0xff000000 | (int) ((style >>> 40) & 0x00ffffffL); - } + return result; + } + public static int decodeForeColor(long style) { + if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND) == 0) { + return (int) ((style >>> 40) & 0b111111111L); + } else { + return 0xff000000 | (int) ((style >>> 40) & 0x00ffffffL); } - public static int decodeBackColor(long style) { - if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND) == 0) { - return (int) ((style >>> 16) & 0b111111111L); - } else { - return 0xff000000 | (int) ((style >>> 16) & 0x00ffffffL); - } - } + } - public static int decodeEffect(long style) { - return (int) (style & 0b11111111111); + public static int decodeBackColor(long style) { + if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND) == 0) { + return (int) ((style >>> 16) & 0b111111111L); + } else { + return 0xff000000 | (int) ((style >>> 16) & 0x00ffffffL); } + } + + public static int decodeEffect(long style) { + return (int) (style & 0b11111111111); + } } diff --git a/app/src/main/java/io/neoterm/backend/WcWidth.java b/app/src/main/java/io/neoterm/backend/WcWidth.java index 132e6f0..924aed3 100755 --- a/app/src/main/java/io/neoterm/backend/WcWidth.java +++ b/app/src/main/java/io/neoterm/backend/WcWidth.java @@ -2,457 +2,461 @@ package io.neoterm.backend; /** * Implementation of wcwidth(3) for Unicode 9. - * + *

* Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters. */ public final class WcWidth { - // From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py - // t commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02): - private static final int[][] ZERO_WIDTH = { - {0x0300, 0x036f}, // Combining Grave Accent ..Combining Latin Small Le - {0x0483, 0x0489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli - {0x0591, 0x05bd}, // Hebrew Accent Etnahta ..Hebrew Point Meteg - {0x05bf, 0x05bf}, // Hebrew Point Rafe ..Hebrew Point Rafe - {0x05c1, 0x05c2}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot - {0x05c4, 0x05c5}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot - {0x05c7, 0x05c7}, // Hebrew Point Qamats Qata..Hebrew Point Qamats Qata - {0x0610, 0x061a}, // Arabic Sign Sallallahou ..Arabic Small Kasra - {0x064b, 0x065f}, // Arabic Fathatan ..Arabic Wavy Hamza Below - {0x0670, 0x0670}, // Arabic Letter Superscrip..Arabic Letter Superscrip - {0x06d6, 0x06dc}, // Arabic Small High Ligatu..Arabic Small High Seen - {0x06df, 0x06e4}, // Arabic Small High Rounde..Arabic Small High Madda - {0x06e7, 0x06e8}, // Arabic Small High Yeh ..Arabic Small High Noon - {0x06ea, 0x06ed}, // Arabic Empty Centre Low ..Arabic Small Low Meem - {0x0711, 0x0711}, // Syriac Letter Superscrip..Syriac Letter Superscrip - {0x0730, 0x074a}, // Syriac Pthaha Above ..Syriac Barrekh - {0x07a6, 0x07b0}, // Thaana Abafili ..Thaana Sukun - {0x07eb, 0x07f3}, // Nko Combining Sh||t High..Nko Combining Double Dot - {0x0816, 0x0819}, // Samaritan Mark In ..Samaritan Mark Dagesh - {0x081b, 0x0823}, // Samaritan Mark Epentheti..Samaritan Vowel Sign A - {0x0825, 0x0827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U - {0x0829, 0x082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa - {0x0859, 0x085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark - {0x08d4, 0x08e1}, // (nil) .. - {0x08e3, 0x0902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara - {0x093a, 0x093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe - {0x093c, 0x093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta - {0x0941, 0x0948}, // Devanagari Vowel Sign U ..Devanagari Vowel Sign Ai - {0x094d, 0x094d}, // Devanagari Sign Virama ..Devanagari Sign Virama - {0x0951, 0x0957}, // Devanagari Stress Sign U..Devanagari Vowel Sign Uu - {0x0962, 0x0963}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo - {0x0981, 0x0981}, // Bengali Sign Candrabindu..Bengali Sign Candrabindu - {0x09bc, 0x09bc}, // Bengali Sign Nukta ..Bengali Sign Nukta - {0x09c1, 0x09c4}, // Bengali Vowel Sign U ..Bengali Vowel Sign Vocal - {0x09cd, 0x09cd}, // Bengali Sign Virama ..Bengali Sign Virama - {0x09e2, 0x09e3}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal - {0x0a01, 0x0a02}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Bindi - {0x0a3c, 0x0a3c}, // Gurmukhi Sign Nukta ..Gurmukhi Sign Nukta - {0x0a41, 0x0a42}, // Gurmukhi Vowel Sign U ..Gurmukhi Vowel Sign Uu - {0x0a47, 0x0a48}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai - {0x0a4b, 0x0a4d}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama - {0x0a51, 0x0a51}, // Gurmukhi Sign Udaat ..Gurmukhi Sign Udaat - {0x0a70, 0x0a71}, // Gurmukhi Tippi ..Gurmukhi Addak - {0x0a75, 0x0a75}, // Gurmukhi Sign Yakash ..Gurmukhi Sign Yakash - {0x0a81, 0x0a82}, // Gujarati Sign Candrabind..Gujarati Sign Anusvara - {0x0abc, 0x0abc}, // Gujarati Sign Nukta ..Gujarati Sign Nukta - {0x0ac1, 0x0ac5}, // Gujarati Vowel Sign U ..Gujarati Vowel Sign Cand - {0x0ac7, 0x0ac8}, // Gujarati Vowel Sign E ..Gujarati Vowel Sign Ai - {0x0acd, 0x0acd}, // Gujarati Sign Virama ..Gujarati Sign Virama - {0x0ae2, 0x0ae3}, // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca - {0x0b01, 0x0b01}, // ||iya Sign Candrabindu ..||iya Sign Candrabindu - {0x0b3c, 0x0b3c}, // ||iya Sign Nukta ..||iya Sign Nukta - {0x0b3f, 0x0b3f}, // ||iya Vowel Sign I ..||iya Vowel Sign I - {0x0b41, 0x0b44}, // ||iya Vowel Sign U ..||iya Vowel Sign Vocalic - {0x0b4d, 0x0b4d}, // ||iya Sign Virama ..||iya Sign Virama - {0x0b56, 0x0b56}, // ||iya Ai Length Mark ..||iya Ai Length Mark - {0x0b62, 0x0b63}, // ||iya Vowel Sign Vocalic..||iya Vowel Sign Vocalic - {0x0b82, 0x0b82}, // Tamil Sign Anusvara ..Tamil Sign Anusvara - {0x0bc0, 0x0bc0}, // Tamil Vowel Sign Ii ..Tamil Vowel Sign Ii - {0x0bcd, 0x0bcd}, // Tamil Sign Virama ..Tamil Sign Virama - {0x0c00, 0x0c00}, // Telugu Sign Combining Ca..Telugu Sign Combining Ca - {0x0c3e, 0x0c40}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Ii - {0x0c46, 0x0c48}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai - {0x0c4a, 0x0c4d}, // Telugu Vowel Sign O ..Telugu Sign Virama - {0x0c55, 0x0c56}, // Telugu Length Mark ..Telugu Ai Length Mark - {0x0c62, 0x0c63}, // Telugu Vowel Sign Vocali..Telugu Vowel Sign Vocali - {0x0c81, 0x0c81}, // Kannada Sign Candrabindu..Kannada Sign Candrabindu - {0x0cbc, 0x0cbc}, // Kannada Sign Nukta ..Kannada Sign Nukta - {0x0cbf, 0x0cbf}, // Kannada Vowel Sign I ..Kannada Vowel Sign I - {0x0cc6, 0x0cc6}, // Kannada Vowel Sign E ..Kannada Vowel Sign E - {0x0ccc, 0x0ccd}, // Kannada Vowel Sign Au ..Kannada Sign Virama - {0x0ce2, 0x0ce3}, // Kannada Vowel Sign Vocal..Kannada Vowel Sign Vocal - {0x0d01, 0x0d01}, // Malayalam Sign Candrabin..Malayalam Sign Candrabin - {0x0d41, 0x0d44}, // Malayalam Vowel Sign U ..Malayalam Vowel Sign Voc - {0x0d4d, 0x0d4d}, // Malayalam Sign Virama ..Malayalam Sign Virama - {0x0d62, 0x0d63}, // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc - {0x0dca, 0x0dca}, // Sinhala Sign Al-lakuna ..Sinhala Sign Al-lakuna - {0x0dd2, 0x0dd4}, // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti - {0x0dd6, 0x0dd6}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga - {0x0e31, 0x0e31}, // Thai Character Mai Han-a..Thai Character Mai Han-a - {0x0e34, 0x0e3a}, // Thai Character Sara I ..Thai Character Phinthu - {0x0e47, 0x0e4e}, // Thai Character Maitaikhu..Thai Character Yamakkan - {0x0eb1, 0x0eb1}, // Lao Vowel Sign Mai Kan ..Lao Vowel Sign Mai Kan - {0x0eb4, 0x0eb9}, // Lao Vowel Sign I ..Lao Vowel Sign Uu - {0x0ebb, 0x0ebc}, // Lao Vowel Sign Mai Kon ..Lao Semivowel Sign Lo - {0x0ec8, 0x0ecd}, // Lao Tone Mai Ek ..Lao Niggahita - {0x0f18, 0x0f19}, // Tibetan Astrological Sig..Tibetan Astrological Sig - {0x0f35, 0x0f35}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung - {0x0f37, 0x0f37}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung - {0x0f39, 0x0f39}, // Tibetan Mark Tsa -phru ..Tibetan Mark Tsa -phru - {0x0f71, 0x0f7e}, // Tibetan Vowel Sign Aa ..Tibetan Sign Rjes Su Nga - {0x0f80, 0x0f84}, // Tibetan Vowel Sign Rever..Tibetan Mark Halanta - {0x0f86, 0x0f87}, // Tibetan Sign Lci Rtags ..Tibetan Sign Yang Rtags - {0x0f8d, 0x0f97}, // Tibetan Subjoined Sign L..Tibetan Subjoined Letter - {0x0f99, 0x0fbc}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter - {0x0fc6, 0x0fc6}, // Tibetan Symbol Padma Gda..Tibetan Symbol Padma Gda - {0x102d, 0x1030}, // Myanmar Vowel Sign I ..Myanmar Vowel Sign Uu - {0x1032, 0x1037}, // Myanmar Vowel Sign Ai ..Myanmar Sign Dot Below - {0x1039, 0x103a}, // Myanmar Sign Virama ..Myanmar Sign Asat - {0x103d, 0x103e}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M - {0x1058, 0x1059}, // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal - {0x105e, 0x1060}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M - {0x1071, 0x1074}, // Myanmar Vowel Sign Geba ..Myanmar Vowel Sign Kayah - {0x1082, 0x1082}, // Myanmar Consonant Sign S..Myanmar Consonant Sign S - {0x1085, 0x1086}, // Myanmar Vowel Sign Shan ..Myanmar Vowel Sign Shan - {0x108d, 0x108d}, // Myanmar Sign Shan Counci..Myanmar Sign Shan Counci - {0x109d, 0x109d}, // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton - {0x135d, 0x135f}, // Ethiopic Combining Gemin..Ethiopic Combining Gemin - {0x1712, 0x1714}, // Tagalog Vowel Sign I ..Tagalog Sign Virama - {0x1732, 0x1734}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod - {0x1752, 0x1753}, // Buhid Vowel Sign I ..Buhid Vowel Sign U - {0x1772, 0x1773}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U - {0x17b4, 0x17b5}, // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa - {0x17b7, 0x17bd}, // Khmer Vowel Sign I ..Khmer Vowel Sign Ua - {0x17c6, 0x17c6}, // Khmer Sign Nikahit ..Khmer Sign Nikahit - {0x17c9, 0x17d3}, // Khmer Sign Muusikatoan ..Khmer Sign Bathamasat - {0x17dd, 0x17dd}, // Khmer Sign Atthacan ..Khmer Sign Atthacan - {0x180b, 0x180d}, // Mongolian Free Variation..Mongolian Free Variation - {0x1885, 0x1886}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal - {0x18a9, 0x18a9}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal - {0x1920, 0x1922}, // Limbu Vowel Sign A ..Limbu Vowel Sign U - {0x1927, 0x1928}, // Limbu Vowel Sign E ..Limbu Vowel Sign O - {0x1932, 0x1932}, // Limbu Small Letter Anusv..Limbu Small Letter Anusv - {0x1939, 0x193b}, // Limbu Sign Mukphreng ..Limbu Sign Sa-i - {0x1a17, 0x1a18}, // Buginese Vowel Sign I ..Buginese Vowel Sign U - {0x1a1b, 0x1a1b}, // Buginese Vowel Sign Ae ..Buginese Vowel Sign Ae - {0x1a56, 0x1a56}, // Tai Tham Consonant Sign ..Tai Tham Consonant Sign - {0x1a58, 0x1a5e}, // Tai Tham Sign Mai Kang L..Tai Tham Consonant Sign - {0x1a60, 0x1a60}, // Tai Tham Sign Sakot ..Tai Tham Sign Sakot - {0x1a62, 0x1a62}, // Tai Tham Vowel Sign Mai ..Tai Tham Vowel Sign Mai - {0x1a65, 0x1a6c}, // Tai Tham Vowel Sign I ..Tai Tham Vowel Sign Oa B - {0x1a73, 0x1a7c}, // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue - {0x1a7f, 0x1a7f}, // Tai Tham Combining Crypt..Tai Tham Combining Crypt - {0x1ab0, 0x1abe}, // Combining Doubled Circum..Combining Parentheses Ov - {0x1b00, 0x1b03}, // Balinese Sign Ulu Ricem ..Balinese Sign Surang - {0x1b34, 0x1b34}, // Balinese Sign Rerekan ..Balinese Sign Rerekan - {0x1b36, 0x1b3a}, // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R - {0x1b3c, 0x1b3c}, // Balinese Vowel Sign La L..Balinese Vowel Sign La L - {0x1b42, 0x1b42}, // Balinese Vowel Sign Pepe..Balinese Vowel Sign Pepe - {0x1b6b, 0x1b73}, // Balinese Musical Symbol ..Balinese Musical Symbol - {0x1b80, 0x1b81}, // Sundanese Sign Panyecek ..Sundanese Sign Panglayar - {0x1ba2, 0x1ba5}, // Sundanese Consonant Sign..Sundanese Vowel Sign Pan - {0x1ba8, 0x1ba9}, // Sundanese Vowel Sign Pam..Sundanese Vowel Sign Pan - {0x1bab, 0x1bad}, // Sundanese Sign Virama ..Sundanese Consonant Sign - {0x1be6, 0x1be6}, // Batak Sign Tompi ..Batak Sign Tompi - {0x1be8, 0x1be9}, // Batak Vowel Sign Pakpak ..Batak Vowel Sign Ee - {0x1bed, 0x1bed}, // Batak Vowel Sign Karo O ..Batak Vowel Sign Karo O - {0x1bef, 0x1bf1}, // Batak Vowel Sign U F|| S..Batak Consonant Sign H - {0x1c2c, 0x1c33}, // Lepcha Vowel Sign E ..Lepcha Consonant Sign T - {0x1c36, 0x1c37}, // Lepcha Sign Ran ..Lepcha Sign Nukta - {0x1cd0, 0x1cd2}, // Vedic Tone Karshana ..Vedic Tone Prenkha - {0x1cd4, 0x1ce0}, // Vedic Sign Yajurvedic Mi..Vedic Tone Rigvedic Kash - {0x1ce2, 0x1ce8}, // Vedic Sign Visarga Svari..Vedic Sign Visarga Anuda - {0x1ced, 0x1ced}, // Vedic Sign Tiryak ..Vedic Sign Tiryak - {0x1cf4, 0x1cf4}, // Vedic Tone Candra Above ..Vedic Tone Candra Above - {0x1cf8, 0x1cf9}, // Vedic Tone Ring Above ..Vedic Tone Double Ring A - {0x1dc0, 0x1df5}, // Combining Dotted Grave A..Combining Up Tack Above - {0x1dfb, 0x1dff}, // (nil) ..Combining Right Arrowhea - {0x20d0, 0x20f0}, // Combining Left Harpoon A..Combining Asterisk Above - {0x2cef, 0x2cf1}, // Coptic Combining Ni Abov..Coptic Combining Spiritu - {0x2d7f, 0x2d7f}, // Tifinagh Consonant Joine..Tifinagh Consonant Joine - {0x2de0, 0x2dff}, // Combining Cyrillic Lette..Combining Cyrillic Lette - {0x302a, 0x302d}, // Ideographic Level Tone M..Ideographic Entering Ton - {0x3099, 0x309a}, // Combining Katakana-hirag..Combining Katakana-hirag - {0xa66f, 0xa672}, // Combining Cyrillic Vzmet..Combining Cyrillic Thous - {0xa674, 0xa67d}, // Combining Cyrillic Lette..Combining Cyrillic Payer - {0xa69e, 0xa69f}, // Combining Cyrillic Lette..Combining Cyrillic Lette - {0xa6f0, 0xa6f1}, // Bamum Combining Mark Koq..Bamum Combining Mark Tuk - {0xa802, 0xa802}, // Syloti Nagri Sign Dvisva..Syloti Nagri Sign Dvisva - {0xa806, 0xa806}, // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant - {0xa80b, 0xa80b}, // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva - {0xa825, 0xa826}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign - {0xa8c4, 0xa8c5}, // Saurashtra Sign Virama .. - {0xa8e0, 0xa8f1}, // Combining Devanagari Dig..Combining Devanagari Sig - {0xa926, 0xa92d}, // Kayah Li Vowel Ue ..Kayah Li Tone Calya Plop - {0xa947, 0xa951}, // Rejang Vowel Sign I ..Rejang Consonant Sign R - {0xa980, 0xa982}, // Javanese Sign Panyangga ..Javanese Sign Layar - {0xa9b3, 0xa9b3}, // Javanese Sign Cecak Telu..Javanese Sign Cecak Telu - {0xa9b6, 0xa9b9}, // Javanese Vowel Sign Wulu..Javanese Vowel Sign Suku - {0xa9bc, 0xa9bc}, // Javanese Vowel Sign Pepe..Javanese Vowel Sign Pepe - {0xa9e5, 0xa9e5}, // Myanmar Sign Shan Saw ..Myanmar Sign Shan Saw - {0xaa29, 0xaa2e}, // Cham Vowel Sign Aa ..Cham Vowel Sign Oe - {0xaa31, 0xaa32}, // Cham Vowel Sign Au ..Cham Vowel Sign Ue - {0xaa35, 0xaa36}, // Cham Consonant Sign La ..Cham Consonant Sign Wa - {0xaa43, 0xaa43}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina - {0xaa4c, 0xaa4c}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina - {0xaa7c, 0xaa7c}, // Myanmar Sign Tai Laing T..Myanmar Sign Tai Laing T - {0xaab0, 0xaab0}, // Tai Viet Mai Kang ..Tai Viet Mai Kang - {0xaab2, 0xaab4}, // Tai Viet Vowel I ..Tai Viet Vowel U - {0xaab7, 0xaab8}, // Tai Viet Mai Khit ..Tai Viet Vowel Ia - {0xaabe, 0xaabf}, // Tai Viet Vowel Am ..Tai Viet Tone Mai Ek - {0xaac1, 0xaac1}, // Tai Viet Tone Mai Tho ..Tai Viet Tone Mai Tho - {0xaaec, 0xaaed}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign - {0xaaf6, 0xaaf6}, // Meetei Mayek Virama ..Meetei Mayek Virama - {0xabe5, 0xabe5}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign - {0xabe8, 0xabe8}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign - {0xabed, 0xabed}, // Meetei Mayek Apun Iyek ..Meetei Mayek Apun Iyek - {0xfb1e, 0xfb1e}, // Hebrew Point Judeo-spani..Hebrew Point Judeo-spani - {0xfe00, 0xfe0f}, // Variation Select||-1 ..Variation Select||-16 - {0xfe20, 0xfe2f}, // Combining Ligature Left ..Combining Cyrillic Titlo - {0x101fd, 0x101fd}, // Phaistos Disc Sign Combi..Phaistos Disc Sign Combi - {0x102e0, 0x102e0}, // Coptic Epact Thousands M..Coptic Epact Thousands M - {0x10376, 0x1037a}, // Combining Old Permic Let..Combining Old Permic Let - {0x10a01, 0x10a03}, // Kharoshthi Vowel Sign I ..Kharoshthi Vowel Sign Vo - {0x10a05, 0x10a06}, // Kharoshthi Vowel Sign E ..Kharoshthi Vowel Sign O - {0x10a0c, 0x10a0f}, // Kharoshthi Vowel Length ..Kharoshthi Sign Visarga - {0x10a38, 0x10a3a}, // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo - {0x10a3f, 0x10a3f}, // Kharoshthi Virama ..Kharoshthi Virama - {0x10ae5, 0x10ae6}, // Manichaean Abbreviation ..Manichaean Abbreviation - {0x11001, 0x11001}, // Brahmi Sign Anusvara ..Brahmi Sign Anusvara - {0x11038, 0x11046}, // Brahmi Vowel Sign Aa ..Brahmi Virama - {0x1107f, 0x11081}, // Brahmi Number Joiner ..Kaithi Sign Anusvara - {0x110b3, 0x110b6}, // Kaithi Vowel Sign U ..Kaithi Vowel Sign Ai - {0x110b9, 0x110ba}, // Kaithi Sign Virama ..Kaithi Sign Nukta - {0x11100, 0x11102}, // Chakma Sign Candrabindu ..Chakma Sign Visarga - {0x11127, 0x1112b}, // Chakma Vowel Sign A ..Chakma Vowel Sign Uu - {0x1112d, 0x11134}, // Chakma Vowel Sign Ai ..Chakma Maayyaa - {0x11173, 0x11173}, // Mahajani Sign Nukta ..Mahajani Sign Nukta - {0x11180, 0x11181}, // Sharada Sign Candrabindu..Sharada Sign Anusvara - {0x111b6, 0x111be}, // Sharada Vowel Sign U ..Sharada Vowel Sign O - {0x111ca, 0x111cc}, // Sharada Sign Nukta ..Sharada Extra Sh||t Vowe - {0x1122f, 0x11231}, // Khojki Vowel Sign U ..Khojki Vowel Sign Ai - {0x11234, 0x11234}, // Khojki Sign Anusvara ..Khojki Sign Anusvara - {0x11236, 0x11237}, // Khojki Sign Nukta ..Khojki Sign Shadda - {0x1123e, 0x1123e}, // (nil) .. - {0x112df, 0x112df}, // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara - {0x112e3, 0x112ea}, // Khudawadi Vowel Sign U ..Khudawadi Sign Virama - {0x11300, 0x11301}, // Grantha Sign Combining A..Grantha Sign Candrabindu - {0x1133c, 0x1133c}, // Grantha Sign Nukta ..Grantha Sign Nukta - {0x11340, 0x11340}, // Grantha Vowel Sign Ii ..Grantha Vowel Sign Ii - {0x11366, 0x1136c}, // Combining Grantha Digit ..Combining Grantha Digit - {0x11370, 0x11374}, // Combining Grantha Letter..Combining Grantha Letter - {0x11438, 0x1143f}, // (nil) .. - {0x11442, 0x11444}, // (nil) .. - {0x11446, 0x11446}, // (nil) .. - {0x114b3, 0x114b8}, // Tirhuta Vowel Sign U ..Tirhuta Vowel Sign Vocal - {0x114ba, 0x114ba}, // Tirhuta Vowel Sign Sh||t..Tirhuta Vowel Sign Sh||t - {0x114bf, 0x114c0}, // Tirhuta Sign Candrabindu..Tirhuta Sign Anusvara - {0x114c2, 0x114c3}, // Tirhuta Sign Virama ..Tirhuta Sign Nukta - {0x115b2, 0x115b5}, // Siddham Vowel Sign U ..Siddham Vowel Sign Vocal - {0x115bc, 0x115bd}, // Siddham Sign Candrabindu..Siddham Sign Anusvara - {0x115bf, 0x115c0}, // Siddham Sign Virama ..Siddham Sign Nukta - {0x115dc, 0x115dd}, // Siddham Vowel Sign Alter..Siddham Vowel Sign Alter - {0x11633, 0x1163a}, // Modi Vowel Sign U ..Modi Vowel Sign Ai - {0x1163d, 0x1163d}, // Modi Sign Anusvara ..Modi Sign Anusvara - {0x1163f, 0x11640}, // Modi Sign Virama ..Modi Sign Ardhacandra - {0x116ab, 0x116ab}, // Takri Sign Anusvara ..Takri Sign Anusvara - {0x116ad, 0x116ad}, // Takri Vowel Sign Aa ..Takri Vowel Sign Aa - {0x116b0, 0x116b5}, // Takri Vowel Sign U ..Takri Vowel Sign Au - {0x116b7, 0x116b7}, // Takri Sign Nukta ..Takri Sign Nukta - {0x1171d, 0x1171f}, // Ahom Consonant Sign Medi..Ahom Consonant Sign Medi - {0x11722, 0x11725}, // Ahom Vowel Sign I ..Ahom Vowel Sign Uu - {0x11727, 0x1172b}, // Ahom Vowel Sign Aw ..Ahom Sign Killer - {0x11c30, 0x11c36}, // (nil) .. - {0x11c38, 0x11c3d}, // (nil) .. - {0x11c3f, 0x11c3f}, // (nil) .. - {0x11c92, 0x11ca7}, // (nil) .. - {0x11caa, 0x11cb0}, // (nil) .. - {0x11cb2, 0x11cb3}, // (nil) .. - {0x11cb5, 0x11cb6}, // (nil) .. - {0x16af0, 0x16af4}, // Bassa Vah Combining High..Bassa Vah Combining High - {0x16b30, 0x16b36}, // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta - {0x16f8f, 0x16f92}, // Miao Tone Right ..Miao Tone Below - {0x1bc9d, 0x1bc9e}, // Duployan Thick Letter Se..Duployan Double Mark - {0x1d167, 0x1d169}, // Musical Symbol Combining..Musical Symbol Combining - {0x1d17b, 0x1d182}, // Musical Symbol Combining..Musical Symbol Combining - {0x1d185, 0x1d18b}, // Musical Symbol Combining..Musical Symbol Combining - {0x1d1aa, 0x1d1ad}, // Musical Symbol Combining..Musical Symbol Combining - {0x1d242, 0x1d244}, // Combining Greek Musical ..Combining Greek Musical - {0x1da00, 0x1da36}, // Signwriting Head Rim ..Signwriting Air Sucking - {0x1da3b, 0x1da6c}, // Signwriting Mouth Closed..Signwriting Excitement - {0x1da75, 0x1da75}, // Signwriting Upper Body T..Signwriting Upper Body T - {0x1da84, 0x1da84}, // Signwriting Location Hea..Signwriting Location Hea - {0x1da9b, 0x1da9f}, // Signwriting Fill Modifie..Signwriting Fill Modifie - {0x1daa1, 0x1daaf}, // Signwriting Rotation Mod..Signwriting Rotation Mod - {0x1e000, 0x1e006}, // (nil) .. - {0x1e008, 0x1e018}, // (nil) .. - {0x1e01b, 0x1e021}, // (nil) .. - {0x1e023, 0x1e024}, // (nil) .. - {0x1e026, 0x1e02a}, // (nil) .. - {0x1e8d0, 0x1e8d6}, // Mende Kikakui Combining ..Mende Kikakui Combining - {0x1e944, 0x1e94a}, // (nil) .. - {0xe0100, 0xe01ef}, // Variation Select||-17 ..Variation Select||-256 - }; + // From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py + // t commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02): + private static final int[][] ZERO_WIDTH = { + {0x0300, 0x036f}, // Combining Grave Accent ..Combining Latin Small Le + {0x0483, 0x0489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli + {0x0591, 0x05bd}, // Hebrew Accent Etnahta ..Hebrew Point Meteg + {0x05bf, 0x05bf}, // Hebrew Point Rafe ..Hebrew Point Rafe + {0x05c1, 0x05c2}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot + {0x05c4, 0x05c5}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot + {0x05c7, 0x05c7}, // Hebrew Point Qamats Qata..Hebrew Point Qamats Qata + {0x0610, 0x061a}, // Arabic Sign Sallallahou ..Arabic Small Kasra + {0x064b, 0x065f}, // Arabic Fathatan ..Arabic Wavy Hamza Below + {0x0670, 0x0670}, // Arabic Letter Superscrip..Arabic Letter Superscrip + {0x06d6, 0x06dc}, // Arabic Small High Ligatu..Arabic Small High Seen + {0x06df, 0x06e4}, // Arabic Small High Rounde..Arabic Small High Madda + {0x06e7, 0x06e8}, // Arabic Small High Yeh ..Arabic Small High Noon + {0x06ea, 0x06ed}, // Arabic Empty Centre Low ..Arabic Small Low Meem + {0x0711, 0x0711}, // Syriac Letter Superscrip..Syriac Letter Superscrip + {0x0730, 0x074a}, // Syriac Pthaha Above ..Syriac Barrekh + {0x07a6, 0x07b0}, // Thaana Abafili ..Thaana Sukun + {0x07eb, 0x07f3}, // Nko Combining Sh||t High..Nko Combining Double Dot + {0x0816, 0x0819}, // Samaritan Mark In ..Samaritan Mark Dagesh + {0x081b, 0x0823}, // Samaritan Mark Epentheti..Samaritan Vowel Sign A + {0x0825, 0x0827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U + {0x0829, 0x082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa + {0x0859, 0x085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark + {0x08d4, 0x08e1}, // (nil) .. + {0x08e3, 0x0902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara + {0x093a, 0x093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe + {0x093c, 0x093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta + {0x0941, 0x0948}, // Devanagari Vowel Sign U ..Devanagari Vowel Sign Ai + {0x094d, 0x094d}, // Devanagari Sign Virama ..Devanagari Sign Virama + {0x0951, 0x0957}, // Devanagari Stress Sign U..Devanagari Vowel Sign Uu + {0x0962, 0x0963}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo + {0x0981, 0x0981}, // Bengali Sign Candrabindu..Bengali Sign Candrabindu + {0x09bc, 0x09bc}, // Bengali Sign Nukta ..Bengali Sign Nukta + {0x09c1, 0x09c4}, // Bengali Vowel Sign U ..Bengali Vowel Sign Vocal + {0x09cd, 0x09cd}, // Bengali Sign Virama ..Bengali Sign Virama + {0x09e2, 0x09e3}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal + {0x0a01, 0x0a02}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Bindi + {0x0a3c, 0x0a3c}, // Gurmukhi Sign Nukta ..Gurmukhi Sign Nukta + {0x0a41, 0x0a42}, // Gurmukhi Vowel Sign U ..Gurmukhi Vowel Sign Uu + {0x0a47, 0x0a48}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai + {0x0a4b, 0x0a4d}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama + {0x0a51, 0x0a51}, // Gurmukhi Sign Udaat ..Gurmukhi Sign Udaat + {0x0a70, 0x0a71}, // Gurmukhi Tippi ..Gurmukhi Addak + {0x0a75, 0x0a75}, // Gurmukhi Sign Yakash ..Gurmukhi Sign Yakash + {0x0a81, 0x0a82}, // Gujarati Sign Candrabind..Gujarati Sign Anusvara + {0x0abc, 0x0abc}, // Gujarati Sign Nukta ..Gujarati Sign Nukta + {0x0ac1, 0x0ac5}, // Gujarati Vowel Sign U ..Gujarati Vowel Sign Cand + {0x0ac7, 0x0ac8}, // Gujarati Vowel Sign E ..Gujarati Vowel Sign Ai + {0x0acd, 0x0acd}, // Gujarati Sign Virama ..Gujarati Sign Virama + {0x0ae2, 0x0ae3}, // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca + {0x0b01, 0x0b01}, // ||iya Sign Candrabindu ..||iya Sign Candrabindu + {0x0b3c, 0x0b3c}, // ||iya Sign Nukta ..||iya Sign Nukta + {0x0b3f, 0x0b3f}, // ||iya Vowel Sign I ..||iya Vowel Sign I + {0x0b41, 0x0b44}, // ||iya Vowel Sign U ..||iya Vowel Sign Vocalic + {0x0b4d, 0x0b4d}, // ||iya Sign Virama ..||iya Sign Virama + {0x0b56, 0x0b56}, // ||iya Ai Length Mark ..||iya Ai Length Mark + {0x0b62, 0x0b63}, // ||iya Vowel Sign Vocalic..||iya Vowel Sign Vocalic + {0x0b82, 0x0b82}, // Tamil Sign Anusvara ..Tamil Sign Anusvara + {0x0bc0, 0x0bc0}, // Tamil Vowel Sign Ii ..Tamil Vowel Sign Ii + {0x0bcd, 0x0bcd}, // Tamil Sign Virama ..Tamil Sign Virama + {0x0c00, 0x0c00}, // Telugu Sign Combining Ca..Telugu Sign Combining Ca + {0x0c3e, 0x0c40}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Ii + {0x0c46, 0x0c48}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai + {0x0c4a, 0x0c4d}, // Telugu Vowel Sign O ..Telugu Sign Virama + {0x0c55, 0x0c56}, // Telugu Length Mark ..Telugu Ai Length Mark + {0x0c62, 0x0c63}, // Telugu Vowel Sign Vocali..Telugu Vowel Sign Vocali + {0x0c81, 0x0c81}, // Kannada Sign Candrabindu..Kannada Sign Candrabindu + {0x0cbc, 0x0cbc}, // Kannada Sign Nukta ..Kannada Sign Nukta + {0x0cbf, 0x0cbf}, // Kannada Vowel Sign I ..Kannada Vowel Sign I + {0x0cc6, 0x0cc6}, // Kannada Vowel Sign E ..Kannada Vowel Sign E + {0x0ccc, 0x0ccd}, // Kannada Vowel Sign Au ..Kannada Sign Virama + {0x0ce2, 0x0ce3}, // Kannada Vowel Sign Vocal..Kannada Vowel Sign Vocal + {0x0d01, 0x0d01}, // Malayalam Sign Candrabin..Malayalam Sign Candrabin + {0x0d41, 0x0d44}, // Malayalam Vowel Sign U ..Malayalam Vowel Sign Voc + {0x0d4d, 0x0d4d}, // Malayalam Sign Virama ..Malayalam Sign Virama + {0x0d62, 0x0d63}, // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc + {0x0dca, 0x0dca}, // Sinhala Sign Al-lakuna ..Sinhala Sign Al-lakuna + {0x0dd2, 0x0dd4}, // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti + {0x0dd6, 0x0dd6}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga + {0x0e31, 0x0e31}, // Thai Character Mai Han-a..Thai Character Mai Han-a + {0x0e34, 0x0e3a}, // Thai Character Sara I ..Thai Character Phinthu + {0x0e47, 0x0e4e}, // Thai Character Maitaikhu..Thai Character Yamakkan + {0x0eb1, 0x0eb1}, // Lao Vowel Sign Mai Kan ..Lao Vowel Sign Mai Kan + {0x0eb4, 0x0eb9}, // Lao Vowel Sign I ..Lao Vowel Sign Uu + {0x0ebb, 0x0ebc}, // Lao Vowel Sign Mai Kon ..Lao Semivowel Sign Lo + {0x0ec8, 0x0ecd}, // Lao Tone Mai Ek ..Lao Niggahita + {0x0f18, 0x0f19}, // Tibetan Astrological Sig..Tibetan Astrological Sig + {0x0f35, 0x0f35}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung + {0x0f37, 0x0f37}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung + {0x0f39, 0x0f39}, // Tibetan Mark Tsa -phru ..Tibetan Mark Tsa -phru + {0x0f71, 0x0f7e}, // Tibetan Vowel Sign Aa ..Tibetan Sign Rjes Su Nga + {0x0f80, 0x0f84}, // Tibetan Vowel Sign Rever..Tibetan Mark Halanta + {0x0f86, 0x0f87}, // Tibetan Sign Lci Rtags ..Tibetan Sign Yang Rtags + {0x0f8d, 0x0f97}, // Tibetan Subjoined Sign L..Tibetan Subjoined Letter + {0x0f99, 0x0fbc}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter + {0x0fc6, 0x0fc6}, // Tibetan Symbol Padma Gda..Tibetan Symbol Padma Gda + {0x102d, 0x1030}, // Myanmar Vowel Sign I ..Myanmar Vowel Sign Uu + {0x1032, 0x1037}, // Myanmar Vowel Sign Ai ..Myanmar Sign Dot Below + {0x1039, 0x103a}, // Myanmar Sign Virama ..Myanmar Sign Asat + {0x103d, 0x103e}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M + {0x1058, 0x1059}, // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal + {0x105e, 0x1060}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M + {0x1071, 0x1074}, // Myanmar Vowel Sign Geba ..Myanmar Vowel Sign Kayah + {0x1082, 0x1082}, // Myanmar Consonant Sign S..Myanmar Consonant Sign S + {0x1085, 0x1086}, // Myanmar Vowel Sign Shan ..Myanmar Vowel Sign Shan + {0x108d, 0x108d}, // Myanmar Sign Shan Counci..Myanmar Sign Shan Counci + {0x109d, 0x109d}, // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton + {0x135d, 0x135f}, // Ethiopic Combining Gemin..Ethiopic Combining Gemin + {0x1712, 0x1714}, // Tagalog Vowel Sign I ..Tagalog Sign Virama + {0x1732, 0x1734}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod + {0x1752, 0x1753}, // Buhid Vowel Sign I ..Buhid Vowel Sign U + {0x1772, 0x1773}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U + {0x17b4, 0x17b5}, // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa + {0x17b7, 0x17bd}, // Khmer Vowel Sign I ..Khmer Vowel Sign Ua + {0x17c6, 0x17c6}, // Khmer Sign Nikahit ..Khmer Sign Nikahit + {0x17c9, 0x17d3}, // Khmer Sign Muusikatoan ..Khmer Sign Bathamasat + {0x17dd, 0x17dd}, // Khmer Sign Atthacan ..Khmer Sign Atthacan + {0x180b, 0x180d}, // Mongolian Free Variation..Mongolian Free Variation + {0x1885, 0x1886}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal + {0x18a9, 0x18a9}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal + {0x1920, 0x1922}, // Limbu Vowel Sign A ..Limbu Vowel Sign U + {0x1927, 0x1928}, // Limbu Vowel Sign E ..Limbu Vowel Sign O + {0x1932, 0x1932}, // Limbu Small Letter Anusv..Limbu Small Letter Anusv + {0x1939, 0x193b}, // Limbu Sign Mukphreng ..Limbu Sign Sa-i + {0x1a17, 0x1a18}, // Buginese Vowel Sign I ..Buginese Vowel Sign U + {0x1a1b, 0x1a1b}, // Buginese Vowel Sign Ae ..Buginese Vowel Sign Ae + {0x1a56, 0x1a56}, // Tai Tham Consonant Sign ..Tai Tham Consonant Sign + {0x1a58, 0x1a5e}, // Tai Tham Sign Mai Kang L..Tai Tham Consonant Sign + {0x1a60, 0x1a60}, // Tai Tham Sign Sakot ..Tai Tham Sign Sakot + {0x1a62, 0x1a62}, // Tai Tham Vowel Sign Mai ..Tai Tham Vowel Sign Mai + {0x1a65, 0x1a6c}, // Tai Tham Vowel Sign I ..Tai Tham Vowel Sign Oa B + {0x1a73, 0x1a7c}, // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue + {0x1a7f, 0x1a7f}, // Tai Tham Combining Crypt..Tai Tham Combining Crypt + {0x1ab0, 0x1abe}, // Combining Doubled Circum..Combining Parentheses Ov + {0x1b00, 0x1b03}, // Balinese Sign Ulu Ricem ..Balinese Sign Surang + {0x1b34, 0x1b34}, // Balinese Sign Rerekan ..Balinese Sign Rerekan + {0x1b36, 0x1b3a}, // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R + {0x1b3c, 0x1b3c}, // Balinese Vowel Sign La L..Balinese Vowel Sign La L + {0x1b42, 0x1b42}, // Balinese Vowel Sign Pepe..Balinese Vowel Sign Pepe + {0x1b6b, 0x1b73}, // Balinese Musical Symbol ..Balinese Musical Symbol + {0x1b80, 0x1b81}, // Sundanese Sign Panyecek ..Sundanese Sign Panglayar + {0x1ba2, 0x1ba5}, // Sundanese Consonant Sign..Sundanese Vowel Sign Pan + {0x1ba8, 0x1ba9}, // Sundanese Vowel Sign Pam..Sundanese Vowel Sign Pan + {0x1bab, 0x1bad}, // Sundanese Sign Virama ..Sundanese Consonant Sign + {0x1be6, 0x1be6}, // Batak Sign Tompi ..Batak Sign Tompi + {0x1be8, 0x1be9}, // Batak Vowel Sign Pakpak ..Batak Vowel Sign Ee + {0x1bed, 0x1bed}, // Batak Vowel Sign Karo O ..Batak Vowel Sign Karo O + {0x1bef, 0x1bf1}, // Batak Vowel Sign U F|| S..Batak Consonant Sign H + {0x1c2c, 0x1c33}, // Lepcha Vowel Sign E ..Lepcha Consonant Sign T + {0x1c36, 0x1c37}, // Lepcha Sign Ran ..Lepcha Sign Nukta + {0x1cd0, 0x1cd2}, // Vedic Tone Karshana ..Vedic Tone Prenkha + {0x1cd4, 0x1ce0}, // Vedic Sign Yajurvedic Mi..Vedic Tone Rigvedic Kash + {0x1ce2, 0x1ce8}, // Vedic Sign Visarga Svari..Vedic Sign Visarga Anuda + {0x1ced, 0x1ced}, // Vedic Sign Tiryak ..Vedic Sign Tiryak + {0x1cf4, 0x1cf4}, // Vedic Tone Candra Above ..Vedic Tone Candra Above + {0x1cf8, 0x1cf9}, // Vedic Tone Ring Above ..Vedic Tone Double Ring A + {0x1dc0, 0x1df5}, // Combining Dotted Grave A..Combining Up Tack Above + {0x1dfb, 0x1dff}, // (nil) ..Combining Right Arrowhea + {0x20d0, 0x20f0}, // Combining Left Harpoon A..Combining Asterisk Above + {0x2cef, 0x2cf1}, // Coptic Combining Ni Abov..Coptic Combining Spiritu + {0x2d7f, 0x2d7f}, // Tifinagh Consonant Joine..Tifinagh Consonant Joine + {0x2de0, 0x2dff}, // Combining Cyrillic Lette..Combining Cyrillic Lette + {0x302a, 0x302d}, // Ideographic Level Tone M..Ideographic Entering Ton + {0x3099, 0x309a}, // Combining Katakana-hirag..Combining Katakana-hirag + {0xa66f, 0xa672}, // Combining Cyrillic Vzmet..Combining Cyrillic Thous + {0xa674, 0xa67d}, // Combining Cyrillic Lette..Combining Cyrillic Payer + {0xa69e, 0xa69f}, // Combining Cyrillic Lette..Combining Cyrillic Lette + {0xa6f0, 0xa6f1}, // Bamum Combining Mark Koq..Bamum Combining Mark Tuk + {0xa802, 0xa802}, // Syloti Nagri Sign Dvisva..Syloti Nagri Sign Dvisva + {0xa806, 0xa806}, // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant + {0xa80b, 0xa80b}, // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva + {0xa825, 0xa826}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign + {0xa8c4, 0xa8c5}, // Saurashtra Sign Virama .. + {0xa8e0, 0xa8f1}, // Combining Devanagari Dig..Combining Devanagari Sig + {0xa926, 0xa92d}, // Kayah Li Vowel Ue ..Kayah Li Tone Calya Plop + {0xa947, 0xa951}, // Rejang Vowel Sign I ..Rejang Consonant Sign R + {0xa980, 0xa982}, // Javanese Sign Panyangga ..Javanese Sign Layar + {0xa9b3, 0xa9b3}, // Javanese Sign Cecak Telu..Javanese Sign Cecak Telu + {0xa9b6, 0xa9b9}, // Javanese Vowel Sign Wulu..Javanese Vowel Sign Suku + {0xa9bc, 0xa9bc}, // Javanese Vowel Sign Pepe..Javanese Vowel Sign Pepe + {0xa9e5, 0xa9e5}, // Myanmar Sign Shan Saw ..Myanmar Sign Shan Saw + {0xaa29, 0xaa2e}, // Cham Vowel Sign Aa ..Cham Vowel Sign Oe + {0xaa31, 0xaa32}, // Cham Vowel Sign Au ..Cham Vowel Sign Ue + {0xaa35, 0xaa36}, // Cham Consonant Sign La ..Cham Consonant Sign Wa + {0xaa43, 0xaa43}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina + {0xaa4c, 0xaa4c}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina + {0xaa7c, 0xaa7c}, // Myanmar Sign Tai Laing T..Myanmar Sign Tai Laing T + {0xaab0, 0xaab0}, // Tai Viet Mai Kang ..Tai Viet Mai Kang + {0xaab2, 0xaab4}, // Tai Viet Vowel I ..Tai Viet Vowel U + {0xaab7, 0xaab8}, // Tai Viet Mai Khit ..Tai Viet Vowel Ia + {0xaabe, 0xaabf}, // Tai Viet Vowel Am ..Tai Viet Tone Mai Ek + {0xaac1, 0xaac1}, // Tai Viet Tone Mai Tho ..Tai Viet Tone Mai Tho + {0xaaec, 0xaaed}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign + {0xaaf6, 0xaaf6}, // Meetei Mayek Virama ..Meetei Mayek Virama + {0xabe5, 0xabe5}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign + {0xabe8, 0xabe8}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign + {0xabed, 0xabed}, // Meetei Mayek Apun Iyek ..Meetei Mayek Apun Iyek + {0xfb1e, 0xfb1e}, // Hebrew Point Judeo-spani..Hebrew Point Judeo-spani + {0xfe00, 0xfe0f}, // Variation Select||-1 ..Variation Select||-16 + {0xfe20, 0xfe2f}, // Combining Ligature Left ..Combining Cyrillic Titlo + {0x101fd, 0x101fd}, // Phaistos Disc Sign Combi..Phaistos Disc Sign Combi + {0x102e0, 0x102e0}, // Coptic Epact Thousands M..Coptic Epact Thousands M + {0x10376, 0x1037a}, // Combining Old Permic Let..Combining Old Permic Let + {0x10a01, 0x10a03}, // Kharoshthi Vowel Sign I ..Kharoshthi Vowel Sign Vo + {0x10a05, 0x10a06}, // Kharoshthi Vowel Sign E ..Kharoshthi Vowel Sign O + {0x10a0c, 0x10a0f}, // Kharoshthi Vowel Length ..Kharoshthi Sign Visarga + {0x10a38, 0x10a3a}, // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo + {0x10a3f, 0x10a3f}, // Kharoshthi Virama ..Kharoshthi Virama + {0x10ae5, 0x10ae6}, // Manichaean Abbreviation ..Manichaean Abbreviation + {0x11001, 0x11001}, // Brahmi Sign Anusvara ..Brahmi Sign Anusvara + {0x11038, 0x11046}, // Brahmi Vowel Sign Aa ..Brahmi Virama + {0x1107f, 0x11081}, // Brahmi Number Joiner ..Kaithi Sign Anusvara + {0x110b3, 0x110b6}, // Kaithi Vowel Sign U ..Kaithi Vowel Sign Ai + {0x110b9, 0x110ba}, // Kaithi Sign Virama ..Kaithi Sign Nukta + {0x11100, 0x11102}, // Chakma Sign Candrabindu ..Chakma Sign Visarga + {0x11127, 0x1112b}, // Chakma Vowel Sign A ..Chakma Vowel Sign Uu + {0x1112d, 0x11134}, // Chakma Vowel Sign Ai ..Chakma Maayyaa + {0x11173, 0x11173}, // Mahajani Sign Nukta ..Mahajani Sign Nukta + {0x11180, 0x11181}, // Sharada Sign Candrabindu..Sharada Sign Anusvara + {0x111b6, 0x111be}, // Sharada Vowel Sign U ..Sharada Vowel Sign O + {0x111ca, 0x111cc}, // Sharada Sign Nukta ..Sharada Extra Sh||t Vowe + {0x1122f, 0x11231}, // Khojki Vowel Sign U ..Khojki Vowel Sign Ai + {0x11234, 0x11234}, // Khojki Sign Anusvara ..Khojki Sign Anusvara + {0x11236, 0x11237}, // Khojki Sign Nukta ..Khojki Sign Shadda + {0x1123e, 0x1123e}, // (nil) .. + {0x112df, 0x112df}, // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara + {0x112e3, 0x112ea}, // Khudawadi Vowel Sign U ..Khudawadi Sign Virama + {0x11300, 0x11301}, // Grantha Sign Combining A..Grantha Sign Candrabindu + {0x1133c, 0x1133c}, // Grantha Sign Nukta ..Grantha Sign Nukta + {0x11340, 0x11340}, // Grantha Vowel Sign Ii ..Grantha Vowel Sign Ii + {0x11366, 0x1136c}, // Combining Grantha Digit ..Combining Grantha Digit + {0x11370, 0x11374}, // Combining Grantha Letter..Combining Grantha Letter + {0x11438, 0x1143f}, // (nil) .. + {0x11442, 0x11444}, // (nil) .. + {0x11446, 0x11446}, // (nil) .. + {0x114b3, 0x114b8}, // Tirhuta Vowel Sign U ..Tirhuta Vowel Sign Vocal + {0x114ba, 0x114ba}, // Tirhuta Vowel Sign Sh||t..Tirhuta Vowel Sign Sh||t + {0x114bf, 0x114c0}, // Tirhuta Sign Candrabindu..Tirhuta Sign Anusvara + {0x114c2, 0x114c3}, // Tirhuta Sign Virama ..Tirhuta Sign Nukta + {0x115b2, 0x115b5}, // Siddham Vowel Sign U ..Siddham Vowel Sign Vocal + {0x115bc, 0x115bd}, // Siddham Sign Candrabindu..Siddham Sign Anusvara + {0x115bf, 0x115c0}, // Siddham Sign Virama ..Siddham Sign Nukta + {0x115dc, 0x115dd}, // Siddham Vowel Sign Alter..Siddham Vowel Sign Alter + {0x11633, 0x1163a}, // Modi Vowel Sign U ..Modi Vowel Sign Ai + {0x1163d, 0x1163d}, // Modi Sign Anusvara ..Modi Sign Anusvara + {0x1163f, 0x11640}, // Modi Sign Virama ..Modi Sign Ardhacandra + {0x116ab, 0x116ab}, // Takri Sign Anusvara ..Takri Sign Anusvara + {0x116ad, 0x116ad}, // Takri Vowel Sign Aa ..Takri Vowel Sign Aa + {0x116b0, 0x116b5}, // Takri Vowel Sign U ..Takri Vowel Sign Au + {0x116b7, 0x116b7}, // Takri Sign Nukta ..Takri Sign Nukta + {0x1171d, 0x1171f}, // Ahom Consonant Sign Medi..Ahom Consonant Sign Medi + {0x11722, 0x11725}, // Ahom Vowel Sign I ..Ahom Vowel Sign Uu + {0x11727, 0x1172b}, // Ahom Vowel Sign Aw ..Ahom Sign Killer + {0x11c30, 0x11c36}, // (nil) .. + {0x11c38, 0x11c3d}, // (nil) .. + {0x11c3f, 0x11c3f}, // (nil) .. + {0x11c92, 0x11ca7}, // (nil) .. + {0x11caa, 0x11cb0}, // (nil) .. + {0x11cb2, 0x11cb3}, // (nil) .. + {0x11cb5, 0x11cb6}, // (nil) .. + {0x16af0, 0x16af4}, // Bassa Vah Combining High..Bassa Vah Combining High + {0x16b30, 0x16b36}, // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta + {0x16f8f, 0x16f92}, // Miao Tone Right ..Miao Tone Below + {0x1bc9d, 0x1bc9e}, // Duployan Thick Letter Se..Duployan Double Mark + {0x1d167, 0x1d169}, // Musical Symbol Combining..Musical Symbol Combining + {0x1d17b, 0x1d182}, // Musical Symbol Combining..Musical Symbol Combining + {0x1d185, 0x1d18b}, // Musical Symbol Combining..Musical Symbol Combining + {0x1d1aa, 0x1d1ad}, // Musical Symbol Combining..Musical Symbol Combining + {0x1d242, 0x1d244}, // Combining Greek Musical ..Combining Greek Musical + {0x1da00, 0x1da36}, // Signwriting Head Rim ..Signwriting Air Sucking + {0x1da3b, 0x1da6c}, // Signwriting Mouth Closed..Signwriting Excitement + {0x1da75, 0x1da75}, // Signwriting Upper Body T..Signwriting Upper Body T + {0x1da84, 0x1da84}, // Signwriting Location Hea..Signwriting Location Hea + {0x1da9b, 0x1da9f}, // Signwriting Fill Modifie..Signwriting Fill Modifie + {0x1daa1, 0x1daaf}, // Signwriting Rotation Mod..Signwriting Rotation Mod + {0x1e000, 0x1e006}, // (nil) .. + {0x1e008, 0x1e018}, // (nil) .. + {0x1e01b, 0x1e021}, // (nil) .. + {0x1e023, 0x1e024}, // (nil) .. + {0x1e026, 0x1e02a}, // (nil) .. + {0x1e8d0, 0x1e8d6}, // Mende Kikakui Combining ..Mende Kikakui Combining + {0x1e944, 0x1e94a}, // (nil) .. + {0xe0100, 0xe01ef}, // Variation Select||-17 ..Variation Select||-256 + }; - // https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py - // at commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02): - private static final int[][] WIDE_EASTASIAN = { - {0x1100, 0x115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler - {0x231a, 0x231b}, // Watch ..Hourglass - {0x2329, 0x232a}, // Left-pointing Angle Brac..Right-pointing Angle Bra - {0x23e9, 0x23ec}, // Black Right-pointing Dou..Black Down-pointing Doub - {0x23f0, 0x23f0}, // Alarm Clock ..Alarm Clock - {0x23f3, 0x23f3}, // Hourglass With Flowing S..Hourglass With Flowing S - {0x25fd, 0x25fe}, // White Medium Small Squar..Black Medium Small Squar - {0x2614, 0x2615}, // Umbrella With Rain Drops..Hot Beverage - {0x2648, 0x2653}, // Aries ..Pisces - {0x267f, 0x267f}, // Wheelchair Symbol ..Wheelchair Symbol - {0x2693, 0x2693}, // Anch|| ..Anch|| - {0x26a1, 0x26a1}, // High Voltage Sign ..High Voltage Sign - {0x26aa, 0x26ab}, // Medium White Circle ..Medium Black Circle - {0x26bd, 0x26be}, // Soccer Ball ..Baseball - {0x26c4, 0x26c5}, // Snowman Without Snow ..Sun Behind Cloud - {0x26ce, 0x26ce}, // Ophiuchus ..Ophiuchus - {0x26d4, 0x26d4}, // No Entry ..No Entry - {0x26ea, 0x26ea}, // Church ..Church - {0x26f2, 0x26f3}, // Fountain ..Flag In Hole - {0x26f5, 0x26f5}, // Sailboat ..Sailboat - {0x26fa, 0x26fa}, // Tent ..Tent - {0x26fd, 0x26fd}, // Fuel Pump ..Fuel Pump - {0x2705, 0x2705}, // White Heavy Check Mark ..White Heavy Check Mark - {0x270a, 0x270b}, // Raised Fist ..Raised Hand - {0x2728, 0x2728}, // Sparkles ..Sparkles - {0x274c, 0x274c}, // Cross Mark ..Cross Mark - {0x274e, 0x274e}, // Negative Squared Cross M..Negative Squared Cross M - {0x2753, 0x2755}, // Black Question Mark ||na..White Exclamation Mark O - {0x2757, 0x2757}, // Heavy Exclamation Mark S..Heavy Exclamation Mark S - {0x2795, 0x2797}, // Heavy Plus Sign ..Heavy Division Sign - {0x27b0, 0x27b0}, // Curly Loop ..Curly Loop - {0x27bf, 0x27bf}, // Double Curly Loop ..Double Curly Loop - {0x2b1b, 0x2b1c}, // Black Large Square ..White Large Square - {0x2b50, 0x2b50}, // White Medium Star ..White Medium Star - {0x2b55, 0x2b55}, // Heavy Large Circle ..Heavy Large Circle - {0x2e80, 0x2e99}, // Cjk Radical Repeat ..Cjk Radical Rap - {0x2e9b, 0x2ef3}, // Cjk Radical Choke ..Cjk Radical C-simplified - {0x2f00, 0x2fd5}, // Kangxi Radical One ..Kangxi Radical Flute - {0x2ff0, 0x2ffb}, // Ideographic Description ..Ideographic Description - {0x3000, 0x303e}, // Ideographic Space ..Ideographic Variation In - {0x3041, 0x3096}, // Hiragana Letter Small A ..Hiragana Letter Small Ke - {0x3099, 0x30ff}, // Combining Katakana-hirag..Katakana Digraph Koto - {0x3105, 0x312d}, // Bopomofo Letter B ..Bopomofo Letter Ih - {0x3131, 0x318e}, // Hangul Letter Kiyeok ..Hangul Letter Araeae - {0x3190, 0x31ba}, // Ideographic Annotation L..Bopomofo Letter Zy - {0x31c0, 0x31e3}, // Cjk Stroke T ..Cjk Stroke Q - {0x31f0, 0x321e}, // Katakana Letter Small Ku..Parenthesized K||ean Cha - {0x3220, 0x3247}, // Parenthesized Ideograph ..Circled Ideograph Koto - {0x3250, 0x32fe}, // Partnership Sign ..Circled Katakana Wo - {0x3300, 0x4dbf}, // Square Apaato .. - {0x4e00, 0xa48c}, // Cjk Unified Ideograph-4e..Yi Syllable Yyr - {0xa490, 0xa4c6}, // Yi Radical Qot ..Yi Radical Ke - {0xa960, 0xa97c}, // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo - {0xac00, 0xd7a3}, // Hangul Syllable Ga ..Hangul Syllable Hih - {0xf900, 0xfaff}, // Cjk Compatibility Ideogr.. - {0xfe10, 0xfe19}, // Presentation F||m F|| Ve..Presentation F||m F|| Ve - {0xfe30, 0xfe52}, // Presentation F||m F|| Ve..Small Full Stop - {0xfe54, 0xfe66}, // Small Semicolon ..Small Equals Sign - {0xfe68, 0xfe6b}, // Small Reverse Solidus ..Small Commercial At - {0xff01, 0xff60}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa - {0xffe0, 0xffe6}, // Fullwidth Cent Sign ..Fullwidth Won Sign - {0x16fe0, 0x16fe0}, // (nil) .. - {0x17000, 0x187ec}, // (nil) .. - {0x18800, 0x18af2}, // (nil) .. - {0x1b000, 0x1b001}, // Katakana Letter Archaic ..Hiragana Letter Archaic - {0x1f004, 0x1f004}, // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon - {0x1f0cf, 0x1f0cf}, // Playing Card Black Joker..Playing Card Black Joker - {0x1f18e, 0x1f18e}, // Negative Squared Ab ..Negative Squared Ab - {0x1f191, 0x1f19a}, // Squared Cl ..Squared Vs - {0x1f200, 0x1f202}, // Square Hiragana Hoka ..Squared Katakana Sa - {0x1f210, 0x1f23b}, // Squared Cjk Unified Ideo.. - {0x1f240, 0x1f248}, // T||toise Shell Bracketed..T||toise Shell Bracketed - {0x1f250, 0x1f251}, // Circled Ideograph Advant..Circled Ideograph Accept - {0x1f300, 0x1f320}, // Cyclone ..Shooting Star - {0x1f32d, 0x1f335}, // Hot Dog ..Cactus - {0x1f337, 0x1f37c}, // Tulip ..Baby Bottle - {0x1f37e, 0x1f393}, // Bottle With Popping C||k..Graduation Cap - {0x1f3a0, 0x1f3ca}, // Carousel H||se ..Swimmer - {0x1f3cf, 0x1f3d3}, // Cricket Bat And Ball ..Table Tennis Paddle And - {0x1f3e0, 0x1f3f0}, // House Building ..European Castle - {0x1f3f4, 0x1f3f4}, // Waving Black Flag ..Waving Black Flag - {0x1f3f8, 0x1f43e}, // Badminton Racquet And Sh..Paw Prints - {0x1f440, 0x1f440}, // Eyes ..Eyes - {0x1f442, 0x1f4fc}, // Ear ..Videocassette - {0x1f4ff, 0x1f53d}, // Prayer Beads ..Down-pointing Small Red - {0x1f54b, 0x1f54e}, // Kaaba ..Men||ah With Nine Branch - {0x1f550, 0x1f567}, // Clock Face One Oclock ..Clock Face Twelve-thirty - {0x1f57a, 0x1f57a}, // (nil) .. - {0x1f595, 0x1f596}, // Reversed Hand With Middl..Raised Hand With Part Be - {0x1f5a4, 0x1f5a4}, // (nil) .. - {0x1f5fb, 0x1f64f}, // Mount Fuji ..Person With Folded Hands - {0x1f680, 0x1f6c5}, // Rocket ..Left Luggage - {0x1f6cc, 0x1f6cc}, // Sleeping Accommodation ..Sleeping Accommodation - {0x1f6d0, 0x1f6d2}, // Place Of W||ship .. - {0x1f6eb, 0x1f6ec}, // Airplane Departure ..Airplane Arriving - {0x1f6f4, 0x1f6f6}, // (nil) .. - {0x1f910, 0x1f91e}, // Zipper-mouth Face .. - {0x1f920, 0x1f927}, // (nil) .. - {0x1f930, 0x1f930}, // (nil) .. - {0x1f933, 0x1f93e}, // (nil) .. - {0x1f940, 0x1f94b}, // (nil) .. - {0x1f950, 0x1f95e}, // (nil) .. - {0x1f980, 0x1f991}, // Crab .. - {0x1f9c0, 0x1f9c0}, // Cheese Wedge ..Cheese Wedge - {0x20000, 0x2fffd}, // Cjk Unified Ideograph-20.. - {0x30000, 0x3fffd}, // (nil) .. - }; + // https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py + // at commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02): + private static final int[][] WIDE_EASTASIAN = { + {0x1100, 0x115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler + {0x231a, 0x231b}, // Watch ..Hourglass + {0x2329, 0x232a}, // Left-pointing Angle Brac..Right-pointing Angle Bra + {0x23e9, 0x23ec}, // Black Right-pointing Dou..Black Down-pointing Doub + {0x23f0, 0x23f0}, // Alarm Clock ..Alarm Clock + {0x23f3, 0x23f3}, // Hourglass With Flowing S..Hourglass With Flowing S + {0x25fd, 0x25fe}, // White Medium Small Squar..Black Medium Small Squar + {0x2614, 0x2615}, // Umbrella With Rain Drops..Hot Beverage + {0x2648, 0x2653}, // Aries ..Pisces + {0x267f, 0x267f}, // Wheelchair Symbol ..Wheelchair Symbol + {0x2693, 0x2693}, // Anch|| ..Anch|| + {0x26a1, 0x26a1}, // High Voltage Sign ..High Voltage Sign + {0x26aa, 0x26ab}, // Medium White Circle ..Medium Black Circle + {0x26bd, 0x26be}, // Soccer Ball ..Baseball + {0x26c4, 0x26c5}, // Snowman Without Snow ..Sun Behind Cloud + {0x26ce, 0x26ce}, // Ophiuchus ..Ophiuchus + {0x26d4, 0x26d4}, // No Entry ..No Entry + {0x26ea, 0x26ea}, // Church ..Church + {0x26f2, 0x26f3}, // Fountain ..Flag In Hole + {0x26f5, 0x26f5}, // Sailboat ..Sailboat + {0x26fa, 0x26fa}, // Tent ..Tent + {0x26fd, 0x26fd}, // Fuel Pump ..Fuel Pump + {0x2705, 0x2705}, // White Heavy Check Mark ..White Heavy Check Mark + {0x270a, 0x270b}, // Raised Fist ..Raised Hand + {0x2728, 0x2728}, // Sparkles ..Sparkles + {0x274c, 0x274c}, // Cross Mark ..Cross Mark + {0x274e, 0x274e}, // Negative Squared Cross M..Negative Squared Cross M + {0x2753, 0x2755}, // Black Question Mark ||na..White Exclamation Mark O + {0x2757, 0x2757}, // Heavy Exclamation Mark S..Heavy Exclamation Mark S + {0x2795, 0x2797}, // Heavy Plus Sign ..Heavy Division Sign + {0x27b0, 0x27b0}, // Curly Loop ..Curly Loop + {0x27bf, 0x27bf}, // Double Curly Loop ..Double Curly Loop + {0x2b1b, 0x2b1c}, // Black Large Square ..White Large Square + {0x2b50, 0x2b50}, // White Medium Star ..White Medium Star + {0x2b55, 0x2b55}, // Heavy Large Circle ..Heavy Large Circle + {0x2e80, 0x2e99}, // Cjk Radical Repeat ..Cjk Radical Rap + {0x2e9b, 0x2ef3}, // Cjk Radical Choke ..Cjk Radical C-simplified + {0x2f00, 0x2fd5}, // Kangxi Radical One ..Kangxi Radical Flute + {0x2ff0, 0x2ffb}, // Ideographic Description ..Ideographic Description + {0x3000, 0x303e}, // Ideographic Space ..Ideographic Variation In + {0x3041, 0x3096}, // Hiragana Letter Small A ..Hiragana Letter Small Ke + {0x3099, 0x30ff}, // Combining Katakana-hirag..Katakana Digraph Koto + {0x3105, 0x312d}, // Bopomofo Letter B ..Bopomofo Letter Ih + {0x3131, 0x318e}, // Hangul Letter Kiyeok ..Hangul Letter Araeae + {0x3190, 0x31ba}, // Ideographic Annotation L..Bopomofo Letter Zy + {0x31c0, 0x31e3}, // Cjk Stroke T ..Cjk Stroke Q + {0x31f0, 0x321e}, // Katakana Letter Small Ku..Parenthesized K||ean Cha + {0x3220, 0x3247}, // Parenthesized Ideograph ..Circled Ideograph Koto + {0x3250, 0x32fe}, // Partnership Sign ..Circled Katakana Wo + {0x3300, 0x4dbf}, // Square Apaato .. + {0x4e00, 0xa48c}, // Cjk Unified Ideograph-4e..Yi Syllable Yyr + {0xa490, 0xa4c6}, // Yi Radical Qot ..Yi Radical Ke + {0xa960, 0xa97c}, // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo + {0xac00, 0xd7a3}, // Hangul Syllable Ga ..Hangul Syllable Hih + {0xf900, 0xfaff}, // Cjk Compatibility Ideogr.. + {0xfe10, 0xfe19}, // Presentation F||m F|| Ve..Presentation F||m F|| Ve + {0xfe30, 0xfe52}, // Presentation F||m F|| Ve..Small Full Stop + {0xfe54, 0xfe66}, // Small Semicolon ..Small Equals Sign + {0xfe68, 0xfe6b}, // Small Reverse Solidus ..Small Commercial At + {0xff01, 0xff60}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa + {0xffe0, 0xffe6}, // Fullwidth Cent Sign ..Fullwidth Won Sign + {0x16fe0, 0x16fe0}, // (nil) .. + {0x17000, 0x187ec}, // (nil) .. + {0x18800, 0x18af2}, // (nil) .. + {0x1b000, 0x1b001}, // Katakana Letter Archaic ..Hiragana Letter Archaic + {0x1f004, 0x1f004}, // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon + {0x1f0cf, 0x1f0cf}, // Playing Card Black Joker..Playing Card Black Joker + {0x1f18e, 0x1f18e}, // Negative Squared Ab ..Negative Squared Ab + {0x1f191, 0x1f19a}, // Squared Cl ..Squared Vs + {0x1f200, 0x1f202}, // Square Hiragana Hoka ..Squared Katakana Sa + {0x1f210, 0x1f23b}, // Squared Cjk Unified Ideo.. + {0x1f240, 0x1f248}, // T||toise Shell Bracketed..T||toise Shell Bracketed + {0x1f250, 0x1f251}, // Circled Ideograph Advant..Circled Ideograph Accept + {0x1f300, 0x1f320}, // Cyclone ..Shooting Star + {0x1f32d, 0x1f335}, // Hot Dog ..Cactus + {0x1f337, 0x1f37c}, // Tulip ..Baby Bottle + {0x1f37e, 0x1f393}, // Bottle With Popping C||k..Graduation Cap + {0x1f3a0, 0x1f3ca}, // Carousel H||se ..Swimmer + {0x1f3cf, 0x1f3d3}, // Cricket Bat And Ball ..Table Tennis Paddle And + {0x1f3e0, 0x1f3f0}, // House Building ..European Castle + {0x1f3f4, 0x1f3f4}, // Waving Black Flag ..Waving Black Flag + {0x1f3f8, 0x1f43e}, // Badminton Racquet And Sh..Paw Prints + {0x1f440, 0x1f440}, // Eyes ..Eyes + {0x1f442, 0x1f4fc}, // Ear ..Videocassette + {0x1f4ff, 0x1f53d}, // Prayer Beads ..Down-pointing Small Red + {0x1f54b, 0x1f54e}, // Kaaba ..Men||ah With Nine Branch + {0x1f550, 0x1f567}, // Clock Face One Oclock ..Clock Face Twelve-thirty + {0x1f57a, 0x1f57a}, // (nil) .. + {0x1f595, 0x1f596}, // Reversed Hand With Middl..Raised Hand With Part Be + {0x1f5a4, 0x1f5a4}, // (nil) .. + {0x1f5fb, 0x1f64f}, // Mount Fuji ..Person With Folded Hands + {0x1f680, 0x1f6c5}, // Rocket ..Left Luggage + {0x1f6cc, 0x1f6cc}, // Sleeping Accommodation ..Sleeping Accommodation + {0x1f6d0, 0x1f6d2}, // Place Of W||ship .. + {0x1f6eb, 0x1f6ec}, // Airplane Departure ..Airplane Arriving + {0x1f6f4, 0x1f6f6}, // (nil) .. + {0x1f910, 0x1f91e}, // Zipper-mouth Face .. + {0x1f920, 0x1f927}, // (nil) .. + {0x1f930, 0x1f930}, // (nil) .. + {0x1f933, 0x1f93e}, // (nil) .. + {0x1f940, 0x1f94b}, // (nil) .. + {0x1f950, 0x1f95e}, // (nil) .. + {0x1f980, 0x1f991}, // Crab .. + {0x1f9c0, 0x1f9c0}, // Cheese Wedge ..Cheese Wedge + {0x20000, 0x2fffd}, // Cjk Unified Ideograph-20.. + {0x30000, 0x3fffd}, // (nil) .. + }; - private static boolean intable(int[][] table, int c) { - // First quick check f|| Latin1 etc. characters. - if (c < table[0][0]) return false; + private static boolean intable(int[][] table, int c) { + // First quick check f|| Latin1 etc. characters. + if (c < table[0][0]) return false; - // Binary search in table. - int bot = 0; - int top = table.length - 1; // (int)(size / sizeof(struct interval) - 1); - while (top >= bot) { - int mid = (bot + top) / 2; - if (table[mid][1] < c) { - bot = mid + 1; - } else if (table[mid][0] > c) { - top = mid - 1; - } else { - return true; - } - } - return false; + // Binary search in table. + int bot = 0; + int top = table.length - 1; // (int)(size / sizeof(struct interval) - 1); + while (top >= bot) { + int mid = (bot + top) / 2; + if (table[mid][1] < c) { + bot = mid + 1; + } else if (table[mid][0] > c) { + top = mid - 1; + } else { + return true; + } + } + return false; + } + + /** + * Return the terminal display width of a code point: 0, 1 || 2. + */ + public static int width(int ucs) { + if (ucs == 0 || + ucs == 0x034F || + (0x200B <= ucs && ucs <= 0x200F) || + ucs == 0x2028 || + ucs == 0x2029 || + (0x202A <= ucs && ucs <= 0x202E) || + (0x2060 <= ucs && ucs <= 0x2063)) { + return 0; } - /** Return the terminal display width of a code point: 0, 1 || 2. */ - public static int width(int ucs) { - if (ucs == 0 || - ucs == 0x034F || - (0x200B <= ucs && ucs <= 0x200F) || - ucs == 0x2028 || - ucs == 0x2029 || - (0x202A <= ucs && ucs <= 0x202E) || - (0x2060 <= ucs && ucs <= 0x2063)) { - return 0; - } + // C0/C1 control characters + // Termux change: Return 0 instead of -1. + if (ucs < 32 || (0x07F <= ucs && ucs < 0x0A0)) return 0; - // C0/C1 control characters - // Termux change: Return 0 instead of -1. - if (ucs < 32 || (0x07F <= ucs && ucs < 0x0A0)) return 0; + // combining characters with zero width + if (intable(ZERO_WIDTH, ucs)) return 0; - // combining characters with zero width - if (intable(ZERO_WIDTH, ucs)) return 0; + return intable(WIDE_EASTASIAN, ucs) ? 2 : 1; + } - return intable(WIDE_EASTASIAN, ucs) ? 2 : 1; - } - - /** The width at an index position in a java char array. */ - public static int width(char[] chars, int index) { - char c = chars[index]; - return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c); - } + /** + * The width at an index position in a java char array. + */ + public static int width(char[] chars, int index) { + char c = chars[index]; + return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c); + } } diff --git a/app/src/main/java/io/neoterm/component/NeoInitializer.kt b/app/src/main/java/io/neoterm/component/NeoInitializer.kt index 2c0a371..1a7d867 100644 --- a/app/src/main/java/io/neoterm/component/NeoInitializer.kt +++ b/app/src/main/java/io/neoterm/component/NeoInitializer.kt @@ -19,24 +19,24 @@ import io.neoterm.frontend.session.shell.ShellProfile * @author kiva */ object NeoInitializer { - fun init(context: Context) { - NLog.init(context) - initComponents() - } + fun init(context: Context) { + NLog.init(context) + initComponents() + } - fun initComponents() { - ComponentManager.registerComponent(ConfigureComponent::class.java) - ComponentManager.registerComponent(CodeGenComponent::class.java) - ComponentManager.registerComponent(ColorSchemeComponent::class.java) - ComponentManager.registerComponent(FontComponent::class.java) - ComponentManager.registerComponent(UserScriptComponent::class.java) - ComponentManager.registerComponent(ExtraKeyComponent::class.java) - ComponentManager.registerComponent(CompletionComponent::class.java) - ComponentManager.registerComponent(PackageComponent::class.java) - ComponentManager.registerComponent(SessionComponent::class.java) - ComponentManager.registerComponent(ProfileComponent::class.java) + fun initComponents() { + ComponentManager.registerComponent(ConfigureComponent::class.java) + ComponentManager.registerComponent(CodeGenComponent::class.java) + ComponentManager.registerComponent(ColorSchemeComponent::class.java) + ComponentManager.registerComponent(FontComponent::class.java) + ComponentManager.registerComponent(UserScriptComponent::class.java) + ComponentManager.registerComponent(ExtraKeyComponent::class.java) + ComponentManager.registerComponent(CompletionComponent::class.java) + ComponentManager.registerComponent(PackageComponent::class.java) + ComponentManager.registerComponent(SessionComponent::class.java) + ComponentManager.registerComponent(ProfileComponent::class.java) - val profileComp = ComponentManager.getComponent() - profileComp.registerProfile(ShellProfile.PROFILE_META_NAME, ShellProfile::class.java) - } + val profileComp = ComponentManager.getComponent() + profileComp.registerProfile(ShellProfile.PROFILE_META_NAME, ShellProfile::class.java) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/codegen/CodeGenComponent.kt b/app/src/main/java/io/neoterm/component/codegen/CodeGenComponent.kt index 4f30b99..ec78f3d 100644 --- a/app/src/main/java/io/neoterm/component/codegen/CodeGenComponent.kt +++ b/app/src/main/java/io/neoterm/component/codegen/CodeGenComponent.kt @@ -8,18 +8,18 @@ import io.neoterm.frontend.component.NeoComponent * @author kiva */ class CodeGenComponent : NeoComponent { - override fun onServiceInit() { - } + override fun onServiceInit() { + } - override fun onServiceDestroy() { - } + override fun onServiceDestroy() { + } - override fun onServiceObtained() { - } + override fun onServiceObtained() { + } - fun newGenerator(codeObject: CodeGenObject): CodeGenerator { - val parameter = CodeGenParameter() - return codeObject.getCodeGenerator(parameter) - } + fun newGenerator(codeObject: CodeGenObject): CodeGenerator { + val parameter = CodeGenParameter() + return codeObject.getCodeGenerator(parameter) + } } diff --git a/app/src/main/java/io/neoterm/component/codegen/generators/NeoColorGenerator.kt b/app/src/main/java/io/neoterm/component/codegen/generators/NeoColorGenerator.kt index 41e4c3f..a0ef650 100644 --- a/app/src/main/java/io/neoterm/component/codegen/generators/NeoColorGenerator.kt +++ b/app/src/main/java/io/neoterm/component/codegen/generators/NeoColorGenerator.kt @@ -11,50 +11,54 @@ import io.neoterm.frontend.component.ComponentManager * @author kiva */ class NeoColorGenerator(parameter: CodeGenParameter) : CodeGenerator(parameter) { - override fun getGeneratorName(): String { - return "NeoColorScheme-Generator" + override fun getGeneratorName(): String { + return "NeoColorScheme-Generator" + } + + override fun generateCode(codeGenObject: CodeGenObject): String { + if (codeGenObject !is NeoColorScheme) { + throw RuntimeException("Invalid object type, expected NeoColorScheme, got ${codeGenObject.javaClass.simpleName}") } - override fun generateCode(codeGenObject: CodeGenObject): String { - if (codeGenObject !is NeoColorScheme) { - throw RuntimeException("Invalid object type, expected NeoColorScheme, got ${codeGenObject.javaClass.simpleName}") - } + return buildString { + start(this) + generateMetaData(this, codeGenObject) + generateColors(this, codeGenObject) + end(this) + } + } - return buildString { - start(this) - generateMetaData(this, codeGenObject) - generateColors(this, codeGenObject) - end(this) - } + private fun start(builder: StringBuilder) { + builder.append("${NeoColorScheme.CONTEXT_META_NAME}: {\n") + } + + private fun end(builder: StringBuilder) { + builder.append("}\n") + } + + private fun generateMetaData(builder: StringBuilder, colorScheme: NeoColorScheme) { + val component = ComponentManager.getComponent() + + builder.append(" ${NeoColorScheme.COLOR_META_NAME}: \"${colorScheme.colorName}\"\n") + builder.append( + " ${NeoColorScheme.COLOR_META_VERSION}: ${ + colorScheme.colorVersion + ?: component.getLoaderVersion() + }\n" + ) + builder.append("\n") + } + + private fun generateColors(builder: StringBuilder, colorScheme: NeoColorScheme) { + builder.append(" ${NeoColorScheme.CONTEXT_COLOR_NAME}: {\n") + + builder.append(" ${NeoColorScheme.COLOR_DEF_BACKGROUND}: ${colorScheme.backgroundColor}\n") + builder.append(" ${NeoColorScheme.COLOR_DEF_FOREGROUND}: ${colorScheme.foregroundColor}\n") + builder.append(" ${NeoColorScheme.COLOR_DEF_CURSOR}: ${colorScheme.cursorColor}\n") + colorScheme.color.entries.forEach { + builder.append(" ${NeoColorScheme.COLOR_PREFIX}${it.key}: ${it.value}\n") } - private fun start(builder: StringBuilder) { - builder.append("${NeoColorScheme.CONTEXT_META_NAME}: {\n") - } - - private fun end(builder: StringBuilder) { - builder.append("}\n") - } - - private fun generateMetaData(builder: StringBuilder, colorScheme: NeoColorScheme) { - val component = ComponentManager.getComponent() - - builder.append(" ${NeoColorScheme.COLOR_META_NAME}: \"${colorScheme.colorName}\"\n") - builder.append(" ${NeoColorScheme.COLOR_META_VERSION}: ${colorScheme.colorVersion - ?: component.getLoaderVersion()}\n") - builder.append("\n") - } - - private fun generateColors(builder: StringBuilder, colorScheme: NeoColorScheme) { - builder.append(" ${NeoColorScheme.CONTEXT_COLOR_NAME}: {\n") - - builder.append(" ${NeoColorScheme.COLOR_DEF_BACKGROUND}: ${colorScheme.backgroundColor}\n") - builder.append(" ${NeoColorScheme.COLOR_DEF_FOREGROUND}: ${colorScheme.foregroundColor}\n") - builder.append(" ${NeoColorScheme.COLOR_DEF_CURSOR}: ${colorScheme.cursorColor}\n") - colorScheme.color.entries.forEach { - builder.append(" ${NeoColorScheme.COLOR_PREFIX}${it.key}: ${it.value}\n") - } - - builder.append(" }\n") - } + builder.append(" }\n") + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/codegen/generators/NeoProfileGenerator.kt b/app/src/main/java/io/neoterm/component/codegen/generators/NeoProfileGenerator.kt index b29839c..3fb2209 100644 --- a/app/src/main/java/io/neoterm/component/codegen/generators/NeoProfileGenerator.kt +++ b/app/src/main/java/io/neoterm/component/codegen/generators/NeoProfileGenerator.kt @@ -8,11 +8,11 @@ import io.neoterm.component.codegen.interfaces.CodeGenerator * @author kiva */ class NeoProfileGenerator(parameter: CodeGenParameter) : CodeGenerator(parameter) { - override fun getGeneratorName(): String { - return "NeoProfile-Generator" - } + override fun getGeneratorName(): String { + return "NeoProfile-Generator" + } - override fun generateCode(codeGenObject: CodeGenObject): String { - return "" - } + override fun generateCode(codeGenObject: CodeGenObject): String { + return "" + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/codegen/interfaces/CodeGenObject.kt b/app/src/main/java/io/neoterm/component/codegen/interfaces/CodeGenObject.kt index e613ba1..abf69ca 100644 --- a/app/src/main/java/io/neoterm/component/codegen/interfaces/CodeGenObject.kt +++ b/app/src/main/java/io/neoterm/component/codegen/interfaces/CodeGenObject.kt @@ -6,5 +6,5 @@ import io.neoterm.component.codegen.CodeGenParameter * @author kiva */ interface CodeGenObject { - fun getCodeGenerator(parameter: CodeGenParameter): CodeGenerator + fun getCodeGenerator(parameter: CodeGenParameter): CodeGenerator } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/codegen/interfaces/CodeGenerator.kt b/app/src/main/java/io/neoterm/component/codegen/interfaces/CodeGenerator.kt index a5b4646..b4a25c3 100644 --- a/app/src/main/java/io/neoterm/component/codegen/interfaces/CodeGenerator.kt +++ b/app/src/main/java/io/neoterm/component/codegen/interfaces/CodeGenerator.kt @@ -6,7 +6,7 @@ import io.neoterm.component.codegen.CodeGenParameter * @author kiva */ abstract class CodeGenerator(parameter: CodeGenParameter) { - abstract fun getGeneratorName(): String + abstract fun getGeneratorName(): String - abstract fun generateCode(codeGenObject: CodeGenObject): String + abstract fun generateCode(codeGenObject: CodeGenObject): String } diff --git a/app/src/main/java/io/neoterm/component/colorscheme/ColorSchemeComponent.kt b/app/src/main/java/io/neoterm/component/colorscheme/ColorSchemeComponent.kt index daf0a39..4e2875f 100644 --- a/app/src/main/java/io/neoterm/component/colorscheme/ColorSchemeComponent.kt +++ b/app/src/main/java/io/neoterm/component/colorscheme/ColorSchemeComponent.kt @@ -20,111 +20,112 @@ import java.nio.file.Files * @author kiva */ class ColorSchemeComponent : ConfigFileBasedComponent(NeoTermPath.COLORS_PATH) { - companion object { - fun colorFile(colorName: String): File { - return File("${NeoTermPath.COLORS_PATH}/$colorName.nl") - } + companion object { + fun colorFile(colorName: String): File { + return File("${NeoTermPath.COLORS_PATH}/$colorName.nl") + } + } + + override val checkComponentFileWhenObtained + get() = true + + private lateinit var DEFAULT_COLOR: NeoColorScheme + private var colors: MutableMap = mutableMapOf() + + override fun onCheckComponentFiles() { + val defaultColorFile = colorFile(DefaultColorScheme.colorName) + if (!defaultColorFile.exists()) { + if (!extractDefaultColor(App.get())) { + DEFAULT_COLOR = DefaultColorScheme + colors[DEFAULT_COLOR.colorName] = DEFAULT_COLOR + return + } } - override val checkComponentFileWhenObtained - get() = true + if (!reloadColorSchemes()) { + DEFAULT_COLOR = DefaultColorScheme + colors[DEFAULT_COLOR.colorName] = DEFAULT_COLOR + } + } - private lateinit var DEFAULT_COLOR: NeoColorScheme - private var colors: MutableMap = mutableMapOf() + override fun onCreateComponentObject(configVisitor: ConfigVisitor) = NeoColorScheme() - override fun onCheckComponentFiles() { - val defaultColorFile = colorFile(DefaultColorScheme.colorName) - if (!defaultColorFile.exists()) { - if (!extractDefaultColor(App.get())) { - DEFAULT_COLOR = DefaultColorScheme - colors[DEFAULT_COLOR.colorName] = DEFAULT_COLOR - return - } - } + fun reloadColorSchemes(): Boolean { + colors.clear() - if (!reloadColorSchemes()) { - DEFAULT_COLOR = DefaultColorScheme - colors[DEFAULT_COLOR.colorName] = DEFAULT_COLOR - } + File(baseDir) + .listFiles(NEOLANG_FILTER) + .mapNotNull { this.loadConfigure(it) } + .forEach { + colors.put(it.colorName, it) + } + + if (colors.containsKey(DefaultColorScheme.colorName)) { + DEFAULT_COLOR = colors[DefaultColorScheme.colorName]!! + return true + } + return false + } + + fun applyColorScheme(view: TerminalView?, extraKeysView: ExtraKeysView?, colorScheme: NeoColorScheme?) { + colorScheme?.applyColorScheme(view, extraKeysView) + } + + fun getCurrentColorScheme(): NeoColorScheme { + return colors[getCurrentColorSchemeName()]!! + } + + fun getCurrentColorSchemeName(): String { + var currentColorName = + NeoPreference.loadString(R.string.key_customization_color_scheme, DefaultColorScheme.colorName) + if (!colors.containsKey(currentColorName)) { + currentColorName = DefaultColorScheme.colorName + NeoPreference.store(R.string.key_customization_color_scheme, DefaultColorScheme.colorName) + } + return currentColorName + } + + fun getColorScheme(colorName: String): NeoColorScheme { + return if (colors.containsKey(colorName)) colors[colorName]!! else getCurrentColorScheme() + } + + fun getColorSchemeNames(): List { + val list = ArrayList() + list += colors.keys + return list + } + + fun setCurrentColorScheme(colorName: String) { + NeoPreference.store(R.string.key_customization_color_scheme, colorName) + } + + fun setCurrentColorScheme(color: NeoColorScheme) { + setCurrentColorScheme(color.colorName) + } + + private fun extractDefaultColor(context: Context): Boolean { + try { + AssetsUtils.extractAssetsDir(context, "colors", baseDir) + return true + } catch (e: Exception) { + NLog.e("ColorScheme", "Failed to extract default colors: ${e.localizedMessage}") + return false + } + } + + fun saveColorScheme(colorScheme: NeoColorScheme) { + val colorFile = colorFile(colorScheme.colorName) + if (colorFile.exists()) { + throw RuntimeException("ColorScheme already ${colorScheme.colorName} exists!") } - override fun onCreateComponentObject(configVisitor: ConfigVisitor) = NeoColorScheme() + val component = ComponentManager.getComponent() + val content = component.newGenerator(colorScheme).generateCode(colorScheme) - fun reloadColorSchemes(): Boolean { - colors.clear() - - File(baseDir) - .listFiles(NEOLANG_FILTER) - .mapNotNull { this.loadConfigure(it) } - .forEach { - colors.put(it.colorName, it) - } - - if (colors.containsKey(DefaultColorScheme.colorName)) { - DEFAULT_COLOR = colors[DefaultColorScheme.colorName]!! - return true - } - return false - } - - fun applyColorScheme(view: TerminalView?, extraKeysView: ExtraKeysView?, colorScheme: NeoColorScheme?) { - colorScheme?.applyColorScheme(view, extraKeysView) - } - - fun getCurrentColorScheme(): NeoColorScheme { - return colors[getCurrentColorSchemeName()]!! - } - - fun getCurrentColorSchemeName(): String { - var currentColorName = NeoPreference.loadString(R.string.key_customization_color_scheme, DefaultColorScheme.colorName) - if (!colors.containsKey(currentColorName)) { - currentColorName = DefaultColorScheme.colorName - NeoPreference.store(R.string.key_customization_color_scheme, DefaultColorScheme.colorName) - } - return currentColorName - } - - fun getColorScheme(colorName: String): NeoColorScheme { - return if (colors.containsKey(colorName)) colors[colorName]!! else getCurrentColorScheme() - } - - fun getColorSchemeNames(): List { - val list = ArrayList() - list += colors.keys - return list - } - - fun setCurrentColorScheme(colorName: String) { - NeoPreference.store(R.string.key_customization_color_scheme, colorName) - } - - fun setCurrentColorScheme(color: NeoColorScheme) { - setCurrentColorScheme(color.colorName) - } - - private fun extractDefaultColor(context: Context): Boolean { - try { - AssetsUtils.extractAssetsDir(context, "colors", baseDir) - return true - } catch (e: Exception) { - NLog.e("ColorScheme", "Failed to extract default colors: ${e.localizedMessage}") - return false - } - } - - fun saveColorScheme(colorScheme: NeoColorScheme) { - val colorFile = colorFile(colorScheme.colorName) - if (colorFile.exists()) { - throw RuntimeException("ColorScheme already ${colorScheme.colorName} exists!") - } - - val component = ComponentManager.getComponent() - val content = component.newGenerator(colorScheme).generateCode(colorScheme) - - kotlin.runCatching { - Files.write(colorFile.toPath(), content.toByteArray()) - }.onFailure { - throw RuntimeException("Failed to save file ${colorFile.absolutePath}") - } + kotlin.runCatching { + Files.write(colorFile.toPath(), content.toByteArray()) + }.onFailure { + throw RuntimeException("Failed to save file ${colorFile.absolutePath}") } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/colorscheme/DefaultColorScheme.kt b/app/src/main/java/io/neoterm/component/colorscheme/DefaultColorScheme.kt index bf8db6b..694a7ec 100644 --- a/app/src/main/java/io/neoterm/component/colorscheme/DefaultColorScheme.kt +++ b/app/src/main/java/io/neoterm/component/colorscheme/DefaultColorScheme.kt @@ -4,12 +4,12 @@ package io.neoterm.component.colorscheme * @author kiva */ object DefaultColorScheme : NeoColorScheme() { - init { - /* NOTE: Keep in sync with assets/colors/Default.nl */ - colorName = "Default" + init { + /* NOTE: Keep in sync with assets/colors/Default.nl */ + colorName = "Default" - foregroundColor = "#ffffff" - backgroundColor = "#14181c" - cursorColor = "#a9aaa9" - } + foregroundColor = "#ffffff" + backgroundColor = "#14181c" + cursorColor = "#a9aaa9" + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/colorscheme/NeoColorScheme.kt b/app/src/main/java/io/neoterm/component/colorscheme/NeoColorScheme.kt index d354cd9..4373009 100644 --- a/app/src/main/java/io/neoterm/component/colorscheme/NeoColorScheme.kt +++ b/app/src/main/java/io/neoterm/component/colorscheme/NeoColorScheme.kt @@ -21,176 +21,176 @@ import java.io.File * @author kiva */ open class NeoColorScheme : CodeGenObject, ConfigFileBasedObject { - companion object { - const val COLOR_PREFIX = "color" - const val CONTEXT_COLOR_NAME = "colors" - const val CONTEXT_META_NAME = "color-scheme" + companion object { + const val COLOR_PREFIX = "color" + const val CONTEXT_COLOR_NAME = "colors" + const val CONTEXT_META_NAME = "color-scheme" - const val COLOR_META_NAME = "name" - const val COLOR_META_VERSION = "version" - const val COLOR_DEF_BACKGROUND = "background" - const val COLOR_DEF_FOREGROUND = "foreground" - const val COLOR_DEF_CURSOR = "cursor" + const val COLOR_META_NAME = "name" + const val COLOR_META_VERSION = "version" + const val COLOR_DEF_BACKGROUND = "background" + const val COLOR_DEF_FOREGROUND = "foreground" + const val COLOR_DEF_CURSOR = "cursor" - val COLOR_META_PATH = arrayOf(CONTEXT_META_NAME) - val COLOR_PATH = arrayOf(CONTEXT_META_NAME, CONTEXT_COLOR_NAME) + val COLOR_META_PATH = arrayOf(CONTEXT_META_NAME) + val COLOR_PATH = arrayOf(CONTEXT_META_NAME, CONTEXT_COLOR_NAME) - const val COLOR_TYPE_BEGIN = -3 - const val COLOR_TYPE_END = 15 + const val COLOR_TYPE_BEGIN = -3 + const val COLOR_TYPE_END = 15 - const val COLOR_BACKGROUND = -3 - const val COLOR_FOREGROUND = -2 - const val COLOR_CURSOR = -1 + const val COLOR_BACKGROUND = -3 + const val COLOR_FOREGROUND = -2 + const val COLOR_CURSOR = -1 - const val COLOR_DIM_BLACK = 0 - const val COLOR_DIM_RED = 1 - const val COLOR_DIM_GREEN = 2 - const val COLOR_DIM_YELLOW = 3 - const val COLOR_DIM_BLUE = 4 - const val COLOR_DIM_MAGENTA = 5 - const val COLOR_DIM_CYAN = 6 - const val COLOR_DIM_WHITE = 7 + const val COLOR_DIM_BLACK = 0 + const val COLOR_DIM_RED = 1 + const val COLOR_DIM_GREEN = 2 + const val COLOR_DIM_YELLOW = 3 + const val COLOR_DIM_BLUE = 4 + const val COLOR_DIM_MAGENTA = 5 + const val COLOR_DIM_CYAN = 6 + const val COLOR_DIM_WHITE = 7 - const val COLOR_BRIGHT_BLACK = 8 - const val COLOR_BRIGHT_RED = 9 - const val COLOR_BRIGHT_GREEN = 10 - const val COLOR_BRIGHT_YELLOW = 11 - const val COLOR_BRIGHT_BLUE = 12 - const val COLOR_BRIGHT_MAGENTA = 13 - const val COLOR_BRIGHT_CYAN = 14 - const val COLOR_BRIGHT_WHITE = 15 + const val COLOR_BRIGHT_BLACK = 8 + const val COLOR_BRIGHT_RED = 9 + const val COLOR_BRIGHT_GREEN = 10 + const val COLOR_BRIGHT_YELLOW = 11 + const val COLOR_BRIGHT_BLUE = 12 + const val COLOR_BRIGHT_MAGENTA = 13 + const val COLOR_BRIGHT_CYAN = 14 + const val COLOR_BRIGHT_WHITE = 15 + } + + lateinit var colorName: String + var colorVersion: String? = null + + var foregroundColor: String? = null + var backgroundColor: String? = null + var cursorColor: String? = null + var color: MutableMap = mutableMapOf() + + fun setColor(type: Int, color: String) { + if (type < 0) { + when (type) { + COLOR_BACKGROUND -> backgroundColor = color + COLOR_FOREGROUND -> foregroundColor = color + COLOR_CURSOR -> cursorColor = color + } + return } + this.color[type] = color + } - lateinit var colorName: String - var colorVersion: String? = null - - var foregroundColor: String? = null - var backgroundColor: String? = null - var cursorColor: String? = null - var color: MutableMap = mutableMapOf() - - fun setColor(type: Int, color: String) { - if (type < 0) { - when (type) { - COLOR_BACKGROUND -> backgroundColor = color - COLOR_FOREGROUND -> foregroundColor = color - COLOR_CURSOR -> cursorColor = color - } - return + fun getColor(type: Int): String? { + validateColors() + return when (type) { + COLOR_BACKGROUND -> backgroundColor + COLOR_FOREGROUND -> foregroundColor + COLOR_CURSOR -> cursorColor + else -> { + if (type in (0 until color.size)) { + color[type] + } else { + "" } - this.color[type] = color + } } + } - fun getColor(type: Int): String? { - validateColors() - return when (type) { - COLOR_BACKGROUND -> backgroundColor - COLOR_FOREGROUND -> foregroundColor - COLOR_CURSOR -> cursorColor - else -> { - if (type in (0 until color.size)) { - color[type] - } else { - "" - } - } - } - } + fun copy(): NeoColorScheme { + val copy = NeoColorScheme() + copy.colorName = colorName + copy.backgroundColor = backgroundColor + copy.foregroundColor = foregroundColor + copy.cursorColor = cursorColor + this.color.forEach { copy.color.put(it.key, it.value) } + return copy + } - fun copy(): NeoColorScheme { - val copy = NeoColorScheme() - copy.colorName = colorName - copy.backgroundColor = backgroundColor - copy.foregroundColor = foregroundColor - copy.cursorColor = cursorColor - this.color.forEach { copy.color.put(it.key, it.value) } - return copy - } + @Throws(RuntimeException::class) + override fun onConfigLoaded(configVisitor: ConfigVisitor) { + val colorName = getMetaByVisitor(configVisitor, COLOR_META_NAME) + ?: throw RuntimeException("ColorScheme must have a name") - @Throws(RuntimeException::class) - override fun onConfigLoaded(configVisitor: ConfigVisitor) { - val colorName = getMetaByVisitor(configVisitor, COLOR_META_NAME) - ?: throw RuntimeException("ColorScheme must have a name") + this.colorName = colorName + this.colorVersion = getMetaByVisitor(configVisitor, COLOR_META_VERSION) - this.colorName = colorName - this.colorVersion = getMetaByVisitor(configVisitor, COLOR_META_VERSION) - - backgroundColor = getColorByVisitor(configVisitor, "background") - foregroundColor = getColorByVisitor(configVisitor, "foreground") - cursorColor = getColorByVisitor(configVisitor, "cursor") - configVisitor.getContext(COLOR_PATH).getAttributes().forEach { - if (it.key.startsWith(COLOR_PREFIX)) { - val colorIndex = try { - it.key.substringAfter(COLOR_PREFIX).toInt() - } catch (e: Exception) { - -1 - } - - if (colorIndex == -1) { - NLog.w("ColorScheme", "Invalid color type: ${it.key}") - } else { - setColor(colorIndex, it.value.asString()) - } - } - } - - validateColors() - } - - internal fun applyColorScheme(view: TerminalView?, extraKeysView: ExtraKeysView?) { - validateColors() - - if (view != null) { - val scheme = TerminalColorScheme() - scheme.updateWith(foregroundColor, backgroundColor, cursorColor, color) - val session = view.currentSession - if (session != null && session.emulator != null) { - session.emulator.setColorScheme(scheme) - } - view.setBackgroundColor(TerminalColors.parse(backgroundColor)) - } - - if (extraKeysView != null) { - extraKeysView.setBackgroundColor(TerminalColors.parse(backgroundColor)) - extraKeysView.setTextColor(TerminalColors.parse(foregroundColor)) - } - } - - override fun getCodeGenerator(parameter: CodeGenParameter): CodeGenerator { - return NeoColorGenerator(parameter) - } - - private fun validateColors() { - backgroundColor = backgroundColor ?: DefaultColorScheme.backgroundColor - foregroundColor = foregroundColor ?: DefaultColorScheme.foregroundColor - cursorColor = cursorColor ?: DefaultColorScheme.cursorColor - } - - private fun getMetaByVisitor(visitor: ConfigVisitor, metaName: String): String? { - return visitor.getStringValue(COLOR_META_PATH, metaName) - } - - private fun getColorByVisitor(visitor: ConfigVisitor, colorName: String): String? { - return visitor.getStringValue(COLOR_PATH, colorName) - } - - @TestOnly - fun testLoadConfigure(file: File): Boolean { - val loaderService = ComponentManager.getComponent() - - val configure: NeoConfigureFile? - try { - configure = loaderService.newLoader(file).loadConfigure() - if (configure == null) { - throw RuntimeException("Parse configuration failed.") - } + backgroundColor = getColorByVisitor(configVisitor, "background") + foregroundColor = getColorByVisitor(configVisitor, "foreground") + cursorColor = getColorByVisitor(configVisitor, "cursor") + configVisitor.getContext(COLOR_PATH).getAttributes().forEach { + if (it.key.startsWith(COLOR_PREFIX)) { + val colorIndex = try { + it.key.substringAfter(COLOR_PREFIX).toInt() } catch (e: Exception) { - NLog.e("ExtraKey", "Failed to load extra key config: ${file.absolutePath}: ${e.localizedMessage}") - return false + -1 } - val visitor = configure.getVisitor() - onConfigLoaded(visitor) - return true + if (colorIndex == -1) { + NLog.w("ColorScheme", "Invalid color type: ${it.key}") + } else { + setColor(colorIndex, it.value.asString()) + } + } } + + validateColors() + } + + internal fun applyColorScheme(view: TerminalView?, extraKeysView: ExtraKeysView?) { + validateColors() + + if (view != null) { + val scheme = TerminalColorScheme() + scheme.updateWith(foregroundColor, backgroundColor, cursorColor, color) + val session = view.currentSession + if (session != null && session.emulator != null) { + session.emulator.setColorScheme(scheme) + } + view.setBackgroundColor(TerminalColors.parse(backgroundColor)) + } + + if (extraKeysView != null) { + extraKeysView.setBackgroundColor(TerminalColors.parse(backgroundColor)) + extraKeysView.setTextColor(TerminalColors.parse(foregroundColor)) + } + } + + override fun getCodeGenerator(parameter: CodeGenParameter): CodeGenerator { + return NeoColorGenerator(parameter) + } + + private fun validateColors() { + backgroundColor = backgroundColor ?: DefaultColorScheme.backgroundColor + foregroundColor = foregroundColor ?: DefaultColorScheme.foregroundColor + cursorColor = cursorColor ?: DefaultColorScheme.cursorColor + } + + private fun getMetaByVisitor(visitor: ConfigVisitor, metaName: String): String? { + return visitor.getStringValue(COLOR_META_PATH, metaName) + } + + private fun getColorByVisitor(visitor: ConfigVisitor, colorName: String): String? { + return visitor.getStringValue(COLOR_PATH, colorName) + } + + @TestOnly + fun testLoadConfigure(file: File): Boolean { + val loaderService = ComponentManager.getComponent() + + val configure: NeoConfigureFile? + try { + configure = loaderService.newLoader(file).loadConfigure() + if (configure == null) { + throw RuntimeException("Parse configuration failed.") + } + } catch (e: Exception) { + NLog.e("ExtraKey", "Failed to load extra key config: ${file.absolutePath}: ${e.localizedMessage}") + return false + } + + val visitor = configure.getVisitor() + onConfigLoaded(visitor) + return true + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/completion/CompletionComponent.kt b/app/src/main/java/io/neoterm/component/completion/CompletionComponent.kt index 5762dfe..428960f 100644 --- a/app/src/main/java/io/neoterm/component/completion/CompletionComponent.kt +++ b/app/src/main/java/io/neoterm/component/completion/CompletionComponent.kt @@ -9,14 +9,14 @@ import io.neoterm.frontend.component.NeoComponent * @author kiva */ class CompletionComponent : NeoComponent { - override fun onServiceInit() { - CompletionManager.registerProvider(FileCompletionProvider()) - CompletionManager.registerProvider(ProgramCompletionProvider()) - } + override fun onServiceInit() { + CompletionManager.registerProvider(FileCompletionProvider()) + CompletionManager.registerProvider(ProgramCompletionProvider()) + } - override fun onServiceDestroy() { - } + override fun onServiceDestroy() { + } - override fun onServiceObtained() { - } + override fun onServiceObtained() { + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/completion/provider/FileCompletionProvider.kt b/app/src/main/java/io/neoterm/component/completion/provider/FileCompletionProvider.kt index daaae32..f6bcdb8 100644 --- a/app/src/main/java/io/neoterm/component/completion/provider/FileCompletionProvider.kt +++ b/app/src/main/java/io/neoterm/component/completion/provider/FileCompletionProvider.kt @@ -9,50 +9,50 @@ import java.io.File */ open class FileCompletionProvider : ICandidateProvider { - override val providerName: String - get() = "NeoTermProvider.FileCompletionProvider" + override val providerName: String + get() = "NeoTermProvider.FileCompletionProvider" - override fun provideCandidates(text: String): List? { - var file = File(text) - var filter: ((File) -> Boolean)? = null + override fun provideCandidates(text: String): List? { + var file = File(text) + var filter: ((File) -> Boolean)? = null - if (!file.isDirectory) { - val partName = file.name - file = file.parentFile - filter = { pathname -> pathname.name.startsWith(partName) } - } - - return generateCandidateList(file, filter) + if (!file.isDirectory) { + val partName = file.name + file = file.parentFile + filter = { pathname -> pathname.name.startsWith(partName) } } - override fun canComplete(text: String): Boolean { - return text.startsWith(File.separatorChar) || text.startsWith("\\./") - } + return generateCandidateList(file, filter) + } - private fun listDirectory(path: File, filter: ((File) -> Boolean)?): Array { - return if (filter != null) path.listFiles(filter) else path.listFiles() - } + override fun canComplete(text: String): Boolean { + return text.startsWith(File.separatorChar) || text.startsWith("\\./") + } - private fun generateCandidateList(file: File, filter: ((File) -> Boolean)?): List? { - if (file.canRead()) { - val candidates = mutableListOf() - listDirectory(file, filter) - .mapTo(candidates, { - val candidate = CompletionCandidate(it.name) - candidate.description = generateDesc(it) - candidate.displayName = generateDisplayName(it) - candidate - }) - return candidates - } - return null - } + private fun listDirectory(path: File, filter: ((File) -> Boolean)?): Array { + return if (filter != null) path.listFiles(filter) else path.listFiles() + } - open fun generateDisplayName(file: File): String { - return if (file.isDirectory) "${file.name}/" else file.name + private fun generateCandidateList(file: File, filter: ((File) -> Boolean)?): List? { + if (file.canRead()) { + val candidates = mutableListOf() + listDirectory(file, filter) + .mapTo(candidates, { + val candidate = CompletionCandidate(it.name) + candidate.description = generateDesc(it) + candidate.displayName = generateDisplayName(it) + candidate + }) + return candidates } + return null + } - open fun generateDesc(file: File): String? { - return null - } + open fun generateDisplayName(file: File): String { + return if (file.isDirectory) "${file.name}/" else file.name + } + + open fun generateDesc(file: File): String? { + return null + } } diff --git a/app/src/main/java/io/neoterm/component/completion/provider/ProgramCompletionProvider.kt b/app/src/main/java/io/neoterm/component/completion/provider/ProgramCompletionProvider.kt index bc3e6af..79aa12c 100644 --- a/app/src/main/java/io/neoterm/component/completion/provider/ProgramCompletionProvider.kt +++ b/app/src/main/java/io/neoterm/component/completion/provider/ProgramCompletionProvider.kt @@ -6,11 +6,11 @@ import java.io.File * @author kiva */ class ProgramCompletionProvider : FileCompletionProvider() { - override val providerName: String - get() = "NeoTermProvider.ProgramCompletionProvider" + override val providerName: String + get() = "NeoTermProvider.ProgramCompletionProvider" - override fun generateDesc(file: File): String? { - return if (file.canExecute()) "" else super.generateDesc(file) - } + override fun generateDesc(file: File): String? { + return if (file.canExecute()) "" else super.generateDesc(file) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/config/ConfigureComponent.kt b/app/src/main/java/io/neoterm/component/config/ConfigureComponent.kt index 860aeaa..02c8d09 100644 --- a/app/src/main/java/io/neoterm/component/config/ConfigureComponent.kt +++ b/app/src/main/java/io/neoterm/component/config/ConfigureComponent.kt @@ -9,25 +9,25 @@ import java.io.File * @author kiva */ class ConfigureComponent : NeoComponent { - val CONFIG_LOADER_VERSION = 20 + val CONFIG_LOADER_VERSION = 20 - override fun onServiceInit() { - } + override fun onServiceInit() { + } - override fun onServiceDestroy() { - } + override fun onServiceDestroy() { + } - override fun onServiceObtained() { - } + override fun onServiceObtained() { + } - fun getLoaderVersion(): Int { - return CONFIG_LOADER_VERSION - } + fun getLoaderVersion(): Int { + return CONFIG_LOADER_VERSION + } - fun newLoader(configFile: File): IConfigureLoader { - return when (configFile.extension) { - "nl" -> NeoLangConfigureLoader(configFile) - else -> OldConfigureLoader(configFile) - } + fun newLoader(configFile: File): IConfigureLoader { + return when (configFile.extension) { + "nl" -> NeoLangConfigureLoader(configFile) + else -> OldConfigureLoader(configFile) } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/config/IConfigureLoader.kt b/app/src/main/java/io/neoterm/component/config/IConfigureLoader.kt index acfd995..78cae91 100644 --- a/app/src/main/java/io/neoterm/component/config/IConfigureLoader.kt +++ b/app/src/main/java/io/neoterm/component/config/IConfigureLoader.kt @@ -6,5 +6,5 @@ import io.neoterm.frontend.config.NeoConfigureFile * @author kiva */ interface IConfigureLoader { - fun loadConfigure() : NeoConfigureFile? + fun loadConfigure(): NeoConfigureFile? } diff --git a/app/src/main/java/io/neoterm/component/config/loaders/NeoLangConfigureLoader.kt b/app/src/main/java/io/neoterm/component/config/loaders/NeoLangConfigureLoader.kt index 2390e4b..95391d4 100644 --- a/app/src/main/java/io/neoterm/component/config/loaders/NeoLangConfigureLoader.kt +++ b/app/src/main/java/io/neoterm/component/config/loaders/NeoLangConfigureLoader.kt @@ -8,8 +8,8 @@ import java.io.File * @author kiva */ class NeoLangConfigureLoader(private val configFile: File) : IConfigureLoader { - override fun loadConfigure(): NeoConfigureFile? { - val configureFile = NeoConfigureFile(configFile) - return if (configureFile.parseConfigure()) configureFile else null - } + override fun loadConfigure(): NeoConfigureFile? { + val configureFile = NeoConfigureFile(configFile) + return if (configureFile.parseConfigure()) configureFile else null + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/config/loaders/OldColorSchemeConfigureFile.kt b/app/src/main/java/io/neoterm/component/config/loaders/OldColorSchemeConfigureFile.kt index a9bdb62..f4d2a50 100644 --- a/app/src/main/java/io/neoterm/component/config/loaders/OldColorSchemeConfigureFile.kt +++ b/app/src/main/java/io/neoterm/component/config/loaders/OldColorSchemeConfigureFile.kt @@ -13,35 +13,35 @@ import java.util.* * @author kiva */ class OldColorSchemeConfigureFile(configureFile: File) : NeoConfigureFile(configureFile) { - override var configVisitor: ConfigVisitor? = null + override var configVisitor: ConfigVisitor? = null - override fun parseConfigure(): Boolean { - try { - val visitor = ConfigVisitor() - visitor.onStart() - visitor.onEnterContext(NeoColorScheme.CONTEXT_META_NAME) + override fun parseConfigure(): Boolean { + try { + val visitor = ConfigVisitor() + visitor.onStart() + visitor.onEnterContext(NeoColorScheme.CONTEXT_META_NAME) - visitor.getCurrentContext() - .defineAttribute(NeoColorScheme.COLOR_META_NAME, NeoLangValue(configureFile.nameWithoutExtension)) - .defineAttribute(NeoColorScheme.COLOR_META_VERSION, NeoLangValue("1.0")) + visitor.getCurrentContext() + .defineAttribute(NeoColorScheme.COLOR_META_NAME, NeoLangValue(configureFile.nameWithoutExtension)) + .defineAttribute(NeoColorScheme.COLOR_META_VERSION, NeoLangValue("1.0")) - visitor.onEnterContext(NeoColorScheme.CONTEXT_COLOR_NAME) + visitor.onEnterContext(NeoColorScheme.CONTEXT_COLOR_NAME) - return FileInputStream(configureFile).use { - val prop = Properties() - prop.load(it) - prop.forEach { - visitor.getCurrentContext().defineAttribute(it.key as String, NeoLangValue(it.value as String)) - } - visitor.onFinish() - this.configVisitor = visitor - true - } - - } catch (e: Exception) { - this.configVisitor = null - NLog.e("ConfigureLoader", "Error while loading old config", e) - return false + return FileInputStream(configureFile).use { + val prop = Properties() + prop.load(it) + prop.forEach { + visitor.getCurrentContext().defineAttribute(it.key as String, NeoLangValue(it.value as String)) } + visitor.onFinish() + this.configVisitor = visitor + true + } + + } catch (e: Exception) { + this.configVisitor = null + NLog.e("ConfigureLoader", "Error while loading old config", e) + return false } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/config/loaders/OldConfigureLoader.kt b/app/src/main/java/io/neoterm/component/config/loaders/OldConfigureLoader.kt index 0434b56..ff73d55 100644 --- a/app/src/main/java/io/neoterm/component/config/loaders/OldConfigureLoader.kt +++ b/app/src/main/java/io/neoterm/component/config/loaders/OldConfigureLoader.kt @@ -8,15 +8,15 @@ import java.io.File * @author kiva */ class OldConfigureLoader(private val configFile: File) : IConfigureLoader { - override fun loadConfigure(): NeoConfigureFile? { - return when (configFile.extension) { - "eks" -> returnConfigure(OldExtraKeysConfigureFile(configFile)) - "color" -> returnConfigure(OldColorSchemeConfigureFile(configFile)) - else -> null - } + override fun loadConfigure(): NeoConfigureFile? { + return when (configFile.extension) { + "eks" -> returnConfigure(OldExtraKeysConfigureFile(configFile)) + "color" -> returnConfigure(OldColorSchemeConfigureFile(configFile)) + else -> null } + } - private fun returnConfigure(configureFile: NeoConfigureFile): NeoConfigureFile? { - return if (configureFile.parseConfigure()) configureFile else null - } + private fun returnConfigure(configureFile: NeoConfigureFile): NeoConfigureFile? { + return if (configureFile.parseConfigure()) configureFile else null + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/config/loaders/OldExtraKeysConfigureFile.kt b/app/src/main/java/io/neoterm/component/config/loaders/OldExtraKeysConfigureFile.kt index df5c0c8..e2a1a28 100644 --- a/app/src/main/java/io/neoterm/component/config/loaders/OldExtraKeysConfigureFile.kt +++ b/app/src/main/java/io/neoterm/component/config/loaders/OldExtraKeysConfigureFile.kt @@ -14,122 +14,122 @@ import java.io.FileReader * @author kiva */ class OldExtraKeysConfigureFile(configureFile: File) : NeoConfigureFile(configureFile) { - override var configVisitor: ConfigVisitor? = null + override var configVisitor: ConfigVisitor? = null - override fun parseConfigure(): Boolean { - try { - val config = parseOldConfig(BufferedReader(FileReader(configureFile))) - return generateVisitor(config) - } catch (e: Exception) { - NLog.e("ConfigureLoader", "Failed to load old extra keys config: ${e.localizedMessage}") - return false - } + override fun parseConfigure(): Boolean { + try { + val config = parseOldConfig(BufferedReader(FileReader(configureFile))) + return generateVisitor(config) + } catch (e: Exception) { + NLog.e("ConfigureLoader", "Failed to load old extra keys config: ${e.localizedMessage}") + return false } + } - private fun generateVisitor(config: NeoExtraKey): Boolean { - configVisitor = ConfigVisitor() - val visitor = configVisitor!! - visitor.onStart() - visitor.onEnterContext(NeoExtraKey.EKS_META_CONTEXT_NAME) + private fun generateVisitor(config: NeoExtraKey): Boolean { + configVisitor = ConfigVisitor() + val visitor = configVisitor!! + visitor.onStart() + visitor.onEnterContext(NeoExtraKey.EKS_META_CONTEXT_NAME) + visitor.getCurrentContext() + .defineAttribute(NeoExtraKey.EKS_META_VERSION, NeoLangValue(config.version)) + .defineAttribute(NeoExtraKey.EKS_META_WITH_DEFAULT, NeoLangValue(config.withDefaultKeys)) + + // program + visitor.onEnterContext(NeoExtraKey.EKS_META_PROGRAM) + config.programNames.forEachIndexed { index, program -> + visitor.getCurrentContext().defineAttribute(index.toString(), NeoLangValue(program)) + } + visitor.onExitContext() + + // key + visitor.onEnterContext(NeoExtraKey.EKS_META_KEY) + config.shortcutKeys.forEachIndexed { index, button -> + if (button is TextButton) { + visitor.onEnterContext(index.toString()) visitor.getCurrentContext() - .defineAttribute(NeoExtraKey.EKS_META_VERSION, NeoLangValue(config.version)) - .defineAttribute(NeoExtraKey.EKS_META_WITH_DEFAULT, NeoLangValue(config.withDefaultKeys)) - - // program - visitor.onEnterContext(NeoExtraKey.EKS_META_PROGRAM) - config.programNames.forEachIndexed { index, program -> - visitor.getCurrentContext().defineAttribute(index.toString(), NeoLangValue(program)) - } + .defineAttribute(NeoExtraKey.EKS_META_WITH_ENTER, NeoLangValue(button.withEnter)) + .defineAttribute(NeoExtraKey.EKS_META_DISPLAY, NeoLangValue(button.buttonKeys!!)) + .defineAttribute(NeoExtraKey.EKS_META_CODE, NeoLangValue(button.buttonKeys!!)) visitor.onExitContext() + } + } + visitor.onExitContext() - // key - visitor.onEnterContext(NeoExtraKey.EKS_META_KEY) - config.shortcutKeys.forEachIndexed { index, button -> - if (button is TextButton) { - visitor.onEnterContext(index.toString()) - visitor.getCurrentContext() - .defineAttribute(NeoExtraKey.EKS_META_WITH_ENTER, NeoLangValue(button.withEnter)) - .defineAttribute(NeoExtraKey.EKS_META_DISPLAY, NeoLangValue(button.buttonKeys!!)) - .defineAttribute(NeoExtraKey.EKS_META_CODE, NeoLangValue(button.buttonKeys!!)) - visitor.onExitContext() - } - } - visitor.onExitContext() + visitor.onFinish() + return true + } - visitor.onFinish() - return true + private fun parseOldConfig(source: BufferedReader): NeoExtraKey { + val config = NeoExtraKey() + var line: String? = source.readLine() + + while (line != null) { + line = line.trim().trimEnd() + if (line.isEmpty() || line.startsWith("#")) { + line = source.readLine() + continue + } + + if (line.startsWith(NeoExtraKey.EKS_META_VERSION)) { + parseHeader(line, config) + } else if (line.startsWith(NeoExtraKey.EKS_META_PROGRAM)) { + parseProgram(line, config) + } else if (line.startsWith("define")) { + parseKeyDefine(line, config) + } else if (line.startsWith(NeoExtraKey.EKS_META_WITH_DEFAULT)) { + parseWithDefault(line, config) + } + line = source.readLine() } - private fun parseOldConfig(source: BufferedReader): NeoExtraKey { - val config = NeoExtraKey() - var line: String? = source.readLine() + if (config.version < 0) { + throw RuntimeException("Not a valid shortcut config file") + } + if (config.programNames.size == 0) { + throw RuntimeException("At least one program name should be given") + } + return config + } - while (line != null) { - line = line.trim().trimEnd() - if (line.isEmpty() || line.startsWith("#")) { - line = source.readLine() - continue - } + private fun parseWithDefault(line: String, config: NeoExtraKey) { + val value = line.substring(NeoExtraKey.EKS_META_WITH_DEFAULT.length).trim().trimEnd() + config.withDefaultKeys = value == "true" + } - if (line.startsWith(NeoExtraKey.EKS_META_VERSION)) { - parseHeader(line, config) - } else if (line.startsWith(NeoExtraKey.EKS_META_PROGRAM)) { - parseProgram(line, config) - } else if (line.startsWith("define")) { - parseKeyDefine(line, config) - } else if (line.startsWith(NeoExtraKey.EKS_META_WITH_DEFAULT)) { - parseWithDefault(line, config) - } - line = source.readLine() - } - - if (config.version < 0) { - throw RuntimeException("Not a valid shortcut config file") - } - if (config.programNames.size == 0) { - throw RuntimeException("At least one program name should be given") - } - return config + private fun parseKeyDefine(line: String, config: NeoExtraKey) { + val keyDefine = line.substring("define".length).trim().trimEnd() + val keyValues = keyDefine.split(" ") + if (keyValues.size < 2) { + throw RuntimeException("Bad define") } - private fun parseWithDefault(line: String, config: NeoExtraKey) { - val value = line.substring(NeoExtraKey.EKS_META_WITH_DEFAULT.length).trim().trimEnd() - config.withDefaultKeys = value == "true" + val buttonText = keyValues[0] + val withEnter = keyValues[1] == "true" + + config.shortcutKeys.add(TextButton(buttonText, withEnter)) + } + + private fun parseProgram(line: String, config: NeoExtraKey) { + val programNames = line.substring(NeoExtraKey.EKS_META_PROGRAM.length).trim().trimEnd() + if (programNames.isEmpty()) { + return } - private fun parseKeyDefine(line: String, config: NeoExtraKey) { - val keyDefine = line.substring("define".length).trim().trimEnd() - val keyValues = keyDefine.split(" ") - if (keyValues.size < 2) { - throw RuntimeException("Bad define") - } + for (name in programNames.split(" ")) { + config.programNames.add(name) + } + } - val buttonText = keyValues[0] - val withEnter = keyValues[1] == "true" - - config.shortcutKeys.add(TextButton(buttonText, withEnter)) + private fun parseHeader(line: String, config: NeoExtraKey) { + val version: Int + val versionString = line.substring(NeoExtraKey.EKS_META_VERSION.length).trim().trimEnd() + try { + version = Integer.parseInt(versionString) + } catch (e: NumberFormatException) { + throw RuntimeException("Bad version '$versionString'") } - private fun parseProgram(line: String, config: NeoExtraKey) { - val programNames = line.substring(NeoExtraKey.EKS_META_PROGRAM.length).trim().trimEnd() - if (programNames.isEmpty()) { - return - } - - for (name in programNames.split(" ")) { - config.programNames.add(name) - } - } - - private fun parseHeader(line: String, config: NeoExtraKey) { - val version: Int - val versionString = line.substring(NeoExtraKey.EKS_META_VERSION.length).trim().trimEnd() - try { - version = Integer.parseInt(versionString) - } catch (e: NumberFormatException) { - throw RuntimeException("Bad version '$versionString'") - } - - config.version = version - } + config.version = version + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/extrakey/ExtraKeyComponent.kt b/app/src/main/java/io/neoterm/component/extrakey/ExtraKeyComponent.kt index 7a76a02..9a7fc41 100644 --- a/app/src/main/java/io/neoterm/component/extrakey/ExtraKeyComponent.kt +++ b/app/src/main/java/io/neoterm/component/extrakey/ExtraKeyComponent.kt @@ -14,58 +14,58 @@ import java.io.File * @author kiva */ class ExtraKeyComponent : ConfigFileBasedComponent(NeoTermPath.EKS_PATH) { - override val checkComponentFileWhenObtained - get() = true + override val checkComponentFileWhenObtained + get() = true - private val extraKeys: MutableMap = mutableMapOf() + private val extraKeys: MutableMap = mutableMapOf() - override fun onCheckComponentFiles() { - val defaultFile = File(NeoTermPath.EKS_DEFAULT_FILE) - if (!defaultFile.exists()) { - extractDefaultConfig(App.get()) - } - reloadExtraKeyConfig() + override fun onCheckComponentFiles() { + val defaultFile = File(NeoTermPath.EKS_DEFAULT_FILE) + if (!defaultFile.exists()) { + extractDefaultConfig(App.get()) + } + reloadExtraKeyConfig() + } + + override fun onCreateComponentObject(configVisitor: ConfigVisitor): NeoExtraKey { + return NeoExtraKey() + } + + fun showShortcutKeys(program: String, extraKeysView: ExtraKeysView?) { + if (extraKeysView == null) { + return } - override fun onCreateComponentObject(configVisitor: ConfigVisitor): NeoExtraKey { - return NeoExtraKey() + val extraKey = extraKeys[program] + if (extraKey != null) { + extraKey.applyExtraKeys(extraKeysView) + return } - fun showShortcutKeys(program: String, extraKeysView: ExtraKeysView?) { - if (extraKeysView == null) { - return - } + extraKeysView.loadDefaultUserKeys() + } - val extraKey = extraKeys[program] - if (extraKey != null) { - extraKey.applyExtraKeys(extraKeysView) - return - } - - extraKeysView.loadDefaultUserKeys() + private fun registerShortcutKeys(extraKey: NeoExtraKey) = + extraKey.programNames.forEach { + extraKeys[it] = extraKey } - private fun registerShortcutKeys(extraKey: NeoExtraKey) = - extraKey.programNames.forEach { - extraKeys[it] = extraKey - } - - private fun extractDefaultConfig(context: Context) { - try { - AssetsUtils.extractAssetsDir(context, "eks", baseDir) - } catch (e: Exception) { - NLog.e("ExtraKey", "Failed to extract configure: ${e.localizedMessage}") - } + private fun extractDefaultConfig(context: Context) { + try { + AssetsUtils.extractAssetsDir(context, "eks", baseDir) + } catch (e: Exception) { + NLog.e("ExtraKey", "Failed to extract configure: ${e.localizedMessage}") } + } - private fun reloadExtraKeyConfig() { - extraKeys.clear() - File(baseDir) - .listFiles(NEOLANG_FILTER) - .filter { it.absolutePath != NeoTermPath.EKS_DEFAULT_FILE } - .mapNotNull { this.loadConfigure(it) } - .forEach { - registerShortcutKeys(it) - } - } + private fun reloadExtraKeyConfig() { + extraKeys.clear() + File(baseDir) + .listFiles(NEOLANG_FILTER) + .filter { it.absolutePath != NeoTermPath.EKS_DEFAULT_FILE } + .mapNotNull { this.loadConfigure(it) } + .forEach { + registerShortcutKeys(it) + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/extrakey/NeoExtraKey.kt b/app/src/main/java/io/neoterm/component/extrakey/NeoExtraKey.kt index 8077873..9eee948 100644 --- a/app/src/main/java/io/neoterm/component/extrakey/NeoExtraKey.kt +++ b/app/src/main/java/io/neoterm/component/extrakey/NeoExtraKey.kt @@ -16,94 +16,94 @@ import java.io.File * @author kiva */ class NeoExtraKey : ConfigFileBasedObject { - companion object { - const val EKS_META_CONTEXT_NAME = "extra-key" + companion object { + const val EKS_META_CONTEXT_NAME = "extra-key" - const val EKS_META_PROGRAM = "program" - const val EKS_META_KEY = "key" - const val EKS_META_WITH_DEFAULT = "with-default" - const val EKS_META_WITH_ENTER = "with-enter" - const val EKS_META_DISPLAY = "display" - const val EKS_META_CODE = "code" - const val EKS_META_VERSION = "version" + const val EKS_META_PROGRAM = "program" + const val EKS_META_KEY = "key" + const val EKS_META_WITH_DEFAULT = "with-default" + const val EKS_META_WITH_ENTER = "with-enter" + const val EKS_META_DISPLAY = "display" + const val EKS_META_CODE = "code" + const val EKS_META_VERSION = "version" - val EKS_META_CONTEXT_PATH = arrayOf(EKS_META_CONTEXT_NAME) + val EKS_META_CONTEXT_PATH = arrayOf(EKS_META_CONTEXT_NAME) + } + + var version: Int = 0 + val programNames: MutableList = mutableListOf() + val shortcutKeys: MutableList = mutableListOf() + var withDefaultKeys: Boolean = true + + fun applyExtraKeys(extraKeysView: ExtraKeysView) { + if (withDefaultKeys) { + extraKeysView.loadDefaultUserKeys() + } + for (button in shortcutKeys) { + extraKeysView.addUserKey(button) + } + } + + override fun onConfigLoaded(configVisitor: ConfigVisitor) { + // program + val programArray = configVisitor.getArray(EKS_META_CONTEXT_PATH, EKS_META_PROGRAM) + if (programArray.isEmpty()) { + throw RuntimeException("Extra Key must have programs attribute") } - var version: Int = 0 - val programNames: MutableList = mutableListOf() - val shortcutKeys: MutableList = mutableListOf() - var withDefaultKeys: Boolean = true - - fun applyExtraKeys(extraKeysView: ExtraKeysView) { - if (withDefaultKeys) { - extraKeysView.loadDefaultUserKeys() - } - for (button in shortcutKeys) { - extraKeysView.addUserKey(button) - } + programArray.forEach { + if (!it.isBlock()) { + programNames.add(it.eval().asString()) + } } - override fun onConfigLoaded(configVisitor: ConfigVisitor) { - // program - val programArray = configVisitor.getArray(EKS_META_CONTEXT_PATH, EKS_META_PROGRAM) - if (programArray.isEmpty()) { - throw RuntimeException("Extra Key must have programs attribute") + // key + val keyArray = configVisitor.getArray(EKS_META_CONTEXT_PATH, EKS_META_KEY) + keyArray.takeWhile { it.isBlock() } + .forEach { + val display = it.eval(EKS_META_DISPLAY) + val code = it.eval(EKS_META_CODE) + if (!code.isValid()) { + throw RuntimeException("Key must have a code") } - programArray.forEach { - if (!it.isBlock()) { - programNames.add(it.eval().asString()) - } - } + val codeText = code.asString() + val displayText = if (display.isValid()) display.asString() else codeText + val withEnter = it.eval(EKS_META_WITH_ENTER) + val withEnterBoolean = withEnter.asString() == "true" - // key - val keyArray = configVisitor.getArray(EKS_META_CONTEXT_PATH, EKS_META_KEY) - keyArray.takeWhile { it.isBlock() } - .forEach { - val display = it.eval(EKS_META_DISPLAY) - val code = it.eval(EKS_META_CODE) - if (!code.isValid()) { - throw RuntimeException("Key must have a code") - } + val button = TextButton(codeText, withEnterBoolean) + button.displayText = displayText + shortcutKeys.add(button) + } - val codeText = code.asString() - val displayText = if (display.isValid()) display.asString() else codeText - val withEnter = it.eval(EKS_META_WITH_ENTER) - val withEnterBoolean = withEnter.asString() == "true" + // We must cal toDouble() before toInt() + // Because in NeoLang, numbers are default to Double + version = getMetaByVisitor(configVisitor, EKS_META_VERSION)?.toDouble()?.toInt() ?: 0 + withDefaultKeys = "true" == getMetaByVisitor(configVisitor, EKS_META_WITH_DEFAULT) + } - val button = TextButton(codeText, withEnterBoolean) - button.displayText = displayText - shortcutKeys.add(button) - } + private fun getMetaByVisitor(visitor: ConfigVisitor, metaName: String): String? { + return visitor.getStringValue(EKS_META_CONTEXT_PATH, metaName) + } - // We must cal toDouble() before toInt() - // Because in NeoLang, numbers are default to Double - version = getMetaByVisitor(configVisitor, EKS_META_VERSION)?.toDouble()?.toInt() ?: 0 - withDefaultKeys = "true" == getMetaByVisitor(configVisitor, EKS_META_WITH_DEFAULT) + @TestOnly + fun testLoadConfigure(file: File): Boolean { + val loaderService = ComponentManager.getComponent() + + val configure: NeoConfigureFile? + try { + configure = loaderService.newLoader(file).loadConfigure() + if (configure == null) { + throw RuntimeException("Parse configuration failed.") + } + } catch (e: Exception) { + NLog.e("ExtraKey", "Failed to load extra key config: ${file.absolutePath}: ${e.localizedMessage}") + return false } - private fun getMetaByVisitor(visitor: ConfigVisitor, metaName: String): String? { - return visitor.getStringValue(EKS_META_CONTEXT_PATH, metaName) - } - - @TestOnly - fun testLoadConfigure(file: File): Boolean { - val loaderService = ComponentManager.getComponent() - - val configure: NeoConfigureFile? - try { - configure = loaderService.newLoader(file).loadConfigure() - if (configure == null) { - throw RuntimeException("Parse configuration failed.") - } - } catch (e: Exception) { - NLog.e("ExtraKey", "Failed to load extra key config: ${file.absolutePath}: ${e.localizedMessage}") - return false - } - - val visitor = configure.getVisitor() - onConfigLoaded(visitor) - return true - } + val visitor = configure.getVisitor() + onConfigLoaded(visitor) + return true + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/font/FontComponent.kt b/app/src/main/java/io/neoterm/component/font/FontComponent.kt index 216e305..02a4a33 100644 --- a/app/src/main/java/io/neoterm/component/font/FontComponent.kt +++ b/app/src/main/java/io/neoterm/component/font/FontComponent.kt @@ -17,113 +17,113 @@ import java.io.File * @author kiva */ class FontComponent : NeoComponent { - private lateinit var DEFAULT_FONT: NeoFont - private lateinit var fonts: MutableMap + private lateinit var DEFAULT_FONT: NeoFont + private lateinit var fonts: MutableMap - fun applyFont(terminalView: TerminalView?, extraKeysView: ExtraKeysView?, font: NeoFont?) { - font?.applyFont(terminalView, extraKeysView) + fun applyFont(terminalView: TerminalView?, extraKeysView: ExtraKeysView?, font: NeoFont?) { + font?.applyFont(terminalView, extraKeysView) + } + + fun getCurrentFont(): NeoFont { + return fonts[getCurrentFontName()]!! + } + + fun setCurrentFont(fontName: String) { + NeoPreference.store(R.string.key_customization_font, fontName) + } + + fun getCurrentFontName(): String { + val defaultFont = DefaultValues.defaultFont + var currentFontName = NeoPreference.loadString(R.string.key_customization_font, defaultFont) + if (!fonts.containsKey(currentFontName)) { + currentFontName = defaultFont + NeoPreference.store(R.string.key_customization_font, defaultFont) + } + return currentFontName + } + + fun getFont(fontName: String): NeoFont { + return if (fonts.containsKey(fontName)) fonts[fontName]!! else getCurrentFont() + } + + fun getFontNames(): List { + val list = ArrayList() + list += fonts.keys + return list + } + + fun reloadFonts(): Boolean { + fonts.clear() + fonts.put("Monospace", NeoFont(Typeface.MONOSPACE)) + fonts.put("Sans Serif", NeoFont(Typeface.SANS_SERIF)) + fonts.put("Serif", NeoFont(Typeface.SERIF)) + val fontDir = File(NeoTermPath.FONT_PATH) + for (file in fontDir.listFiles({ pathname -> pathname.name.endsWith(".ttf") })) { + val fontName = fontName(file) + val font = NeoFont(file) + fonts.put(fontName, font) } - fun getCurrentFont(): NeoFont { - return fonts[getCurrentFontName()]!! + val defaultFont = DefaultValues.defaultFont + if (fonts.containsKey(defaultFont)) { + DEFAULT_FONT = fonts[defaultFont]!! + return true + } + return false + } + + override fun onServiceInit() { + checkForFiles() + } + + override fun onServiceDestroy() { + } + + override fun onServiceObtained() { + checkForFiles() + } + + private fun loadDefaultFontFromAsset(context: Context): NeoFont { + val defaultFont = DefaultValues.defaultFont + return NeoFont(Typeface.createFromAsset(context.assets, "fonts/$defaultFont.ttf")) + } + + private fun extractDefaultFont(context: Context): Boolean { + try { + AssetsUtils.extractAssetsDir(context, "fonts", NeoTermPath.FONT_PATH) + return true + } catch (e: Exception) { + return false + } + } + + private fun fontFile(fontName: String): File { + return File("${NeoTermPath.FONT_PATH}/$fontName.ttf") + } + + private fun fontName(fontFile: File): String { + return fontFile.nameWithoutExtension + } + + private fun checkForFiles() { + File(NeoTermPath.FONT_PATH).mkdirs() + fonts = mutableMapOf() + + val context = App.get() + val defaultFont = DefaultValues.defaultFont + val defaultFontFile = fontFile(defaultFont) + + if (!defaultFontFile.exists()) { + if (!extractDefaultFont(context)) { + DEFAULT_FONT = loadDefaultFontFromAsset(context) + fonts.put(defaultFont, DEFAULT_FONT) + return + } } - fun setCurrentFont(fontName: String) { - NeoPreference.store(R.string.key_customization_font, fontName) - } - - fun getCurrentFontName(): String { - val defaultFont = DefaultValues.defaultFont - var currentFontName = NeoPreference.loadString(R.string.key_customization_font, defaultFont) - if (!fonts.containsKey(currentFontName)) { - currentFontName = defaultFont - NeoPreference.store(R.string.key_customization_font, defaultFont) - } - return currentFontName - } - - fun getFont(fontName: String): NeoFont { - return if (fonts.containsKey(fontName)) fonts[fontName]!! else getCurrentFont() - } - - fun getFontNames(): List { - val list = ArrayList() - list += fonts.keys - return list - } - - fun reloadFonts(): Boolean { - fonts.clear() - fonts.put("Monospace", NeoFont(Typeface.MONOSPACE)) - fonts.put("Sans Serif", NeoFont(Typeface.SANS_SERIF)) - fonts.put("Serif", NeoFont(Typeface.SERIF)) - val fontDir = File(NeoTermPath.FONT_PATH) - for (file in fontDir.listFiles({ pathname -> pathname.name.endsWith(".ttf") })) { - val fontName = fontName(file) - val font = NeoFont(file) - fonts.put(fontName, font) - } - - val defaultFont = DefaultValues.defaultFont - if (fonts.containsKey(defaultFont)) { - DEFAULT_FONT = fonts[defaultFont]!! - return true - } - return false - } - - override fun onServiceInit() { - checkForFiles() - } - - override fun onServiceDestroy() { - } - - override fun onServiceObtained() { - checkForFiles() - } - - private fun loadDefaultFontFromAsset(context: Context): NeoFont { - val defaultFont = DefaultValues.defaultFont - return NeoFont(Typeface.createFromAsset(context.assets, "fonts/$defaultFont.ttf")) - } - - private fun extractDefaultFont(context: Context): Boolean { - try { - AssetsUtils.extractAssetsDir(context, "fonts", NeoTermPath.FONT_PATH) - return true - } catch (e: Exception) { - return false - } - } - - private fun fontFile(fontName: String): File { - return File("${NeoTermPath.FONT_PATH}/$fontName.ttf") - } - - private fun fontName(fontFile: File): String { - return fontFile.nameWithoutExtension - } - - private fun checkForFiles() { - File(NeoTermPath.FONT_PATH).mkdirs() - fonts = mutableMapOf() - - val context = App.get() - val defaultFont = DefaultValues.defaultFont - val defaultFontFile = fontFile(defaultFont) - - if (!defaultFontFile.exists()) { - if (!extractDefaultFont(context)) { - DEFAULT_FONT = loadDefaultFontFromAsset(context) - fonts.put(defaultFont, DEFAULT_FONT) - return - } - } - - if (!reloadFonts()) { - DEFAULT_FONT = loadDefaultFontFromAsset(context) - fonts.put(defaultFont, DEFAULT_FONT) - } + if (!reloadFonts()) { + DEFAULT_FONT = loadDefaultFontFromAsset(context) + fonts.put(defaultFont, DEFAULT_FONT) } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/font/NeoFont.kt b/app/src/main/java/io/neoterm/component/font/NeoFont.kt index 8165658..d4ac7fe 100644 --- a/app/src/main/java/io/neoterm/component/font/NeoFont.kt +++ b/app/src/main/java/io/neoterm/component/font/NeoFont.kt @@ -9,31 +9,31 @@ import java.io.File * @author kiva */ class NeoFont { - private var fontFile: File? = null - private var typeface: Typeface? = null + private var fontFile: File? = null + private var typeface: Typeface? = null - constructor(fontFile: File) { - this.fontFile = fontFile + constructor(fontFile: File) { + this.fontFile = fontFile + } + + constructor(typeface: Typeface) { + this.typeface = typeface + } + + internal fun applyFont(terminalView: TerminalView?, extraKeysView: ExtraKeysView?) { + val typeface = getTypeFace() + terminalView?.setTypeface(typeface) + extraKeysView?.setTypeface(typeface) + } + + private fun getTypeFace(): Typeface? { + if (typeface == null && fontFile == null) { + return null } - constructor(typeface: Typeface) { - this.typeface = typeface - } - - internal fun applyFont(terminalView: TerminalView?, extraKeysView: ExtraKeysView?) { - val typeface = getTypeFace() - terminalView?.setTypeface(typeface) - extraKeysView?.setTypeface(typeface) - } - - private fun getTypeFace(): Typeface? { - if (typeface == null && fontFile == null) { - return null - } - - if (typeface == null) { - typeface = Typeface.createFromFile(fontFile) - } - return typeface + if (typeface == null) { + typeface = Typeface.createFromFile(fontFile) } + return typeface + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/pm/Architecture.kt b/app/src/main/java/io/neoterm/component/pm/Architecture.kt index 6e5351f..b9ae753 100644 --- a/app/src/main/java/io/neoterm/component/pm/Architecture.kt +++ b/app/src/main/java/io/neoterm/component/pm/Architecture.kt @@ -5,17 +5,17 @@ package io.neoterm.component.pm */ enum class Architecture { - ALL, ARM, AARCH64, X86, X86_64; + ALL, ARM, AARCH64, X86, X86_64; - companion object { - fun parse(arch: String): Architecture { - return when (arch) { - "arm" -> ARM - "aarch64" -> AARCH64 - "x86" -> X86 - "x86_64" -> X86_64 - else -> ALL - } - } + companion object { + fun parse(arch: String): Architecture { + return when (arch) { + "arm" -> ARM + "aarch64" -> AARCH64 + "x86" -> X86 + "x86_64" -> X86_64 + else -> ALL + } } + } } diff --git a/app/src/main/java/io/neoterm/component/pm/NeoPackageInfo.kt b/app/src/main/java/io/neoterm/component/pm/NeoPackageInfo.kt index ea5d9ed..fa304a4 100644 --- a/app/src/main/java/io/neoterm/component/pm/NeoPackageInfo.kt +++ b/app/src/main/java/io/neoterm/component/pm/NeoPackageInfo.kt @@ -5,20 +5,20 @@ package io.neoterm.component.pm */ class NeoPackageInfo { - var packageName: String? = null - var isEssential: Boolean = false - var version: String? = null - var architecture: Architecture = Architecture.ALL - var maintainer: String? = null - var installedSizeInBytes: Long = 0L - var fileName: String? = null - var dependenciesString: String? = null - var dependencies: Array? = null - var sizeInBytes: Long = 0L - var md5: String? = null - var sha1: String? = null - var sha256: String? = null - var homePage: String? = null - var description: String? = null + var packageName: String? = null + var isEssential: Boolean = false + var version: String? = null + var architecture: Architecture = Architecture.ALL + var maintainer: String? = null + var installedSizeInBytes: Long = 0L + var fileName: String? = null + var dependenciesString: String? = null + var dependencies: Array? = null + var sizeInBytes: Long = 0L + var md5: String? = null + var sha1: String? = null + var sha256: String? = null + var homePage: String? = null + var description: String? = null } diff --git a/app/src/main/java/io/neoterm/component/pm/NeoPackageParser.java b/app/src/main/java/io/neoterm/component/pm/NeoPackageParser.java index c13007e..2c5d816 100644 --- a/app/src/main/java/io/neoterm/component/pm/NeoPackageParser.java +++ b/app/src/main/java/io/neoterm/component/pm/NeoPackageParser.java @@ -10,161 +10,161 @@ import java.io.InputStreamReader; */ public class NeoPackageParser { - public interface ParseStateListener { - void onStartState(); + public interface ParseStateListener { + void onStartState(); - void onEndState(); + void onEndState(); - NeoPackageInfo onCreatePackageInfo(); + NeoPackageInfo onCreatePackageInfo(); - void onStartParsePackage(String name, NeoPackageInfo packageInfo); + void onStartParsePackage(String name, NeoPackageInfo packageInfo); - void onEndParsePackage(NeoPackageInfo packageInfo); + void onEndParsePackage(NeoPackageInfo packageInfo); + } + + private static final String + KEY_PACKAGE_NAME = "Package", + KEY_VERSION = "Version", + KEY_ESSENTIAL = "Essential", + KEY_ARCH = "Architecture", + KEY_MAINTAINER = "Maintainer", + KEY_INSTALLED_SIZE = "Installed-Size", + KEY_DEPENDS = "Depends", + KEY_FILENAME = "Filename", + KEY_SIZE = "Size", + KEY_MD5 = "MD5sum", + KEY_SHA1 = "SHA1", + KEY_SHA256 = "SHA256", + KEY_HOMEPAGE = "Homepage", + KEY_DESC = "Description"; + + private BufferedReader reader; + private ParseStateListener stateListener; + + NeoPackageParser(InputStream inputStream) { + reader = new BufferedReader(new InputStreamReader(inputStream)); + } + + void setStateListener(ParseStateListener stateListener) { + this.stateListener = stateListener; + } + + public void parse() throws IOException { + if (stateListener == null) { + return; } - private static final String - KEY_PACKAGE_NAME = "Package", - KEY_VERSION = "Version", - KEY_ESSENTIAL = "Essential", - KEY_ARCH = "Architecture", - KEY_MAINTAINER = "Maintainer", - KEY_INSTALLED_SIZE = "Installed-Size", - KEY_DEPENDS = "Depends", - KEY_FILENAME = "Filename", - KEY_SIZE = "Size", - KEY_MD5 = "MD5sum", - KEY_SHA1 = "SHA1", - KEY_SHA256 = "SHA256", - KEY_HOMEPAGE = "Homepage", - KEY_DESC = "Description"; + String line; + String[] splits = new String[2]; + String key = null; + String value = null; + boolean appendMode = false; - private BufferedReader reader; - private ParseStateListener stateListener; + NeoPackageInfo packageInfo = null; - NeoPackageParser(InputStream inputStream) { - reader = new BufferedReader(new InputStreamReader(inputStream)); - } + stateListener.onStartState(); + while ((line = reader.readLine()) != null) { + if (line.isEmpty()) { + continue; + } - void setStateListener(ParseStateListener stateListener) { - this.stateListener = stateListener; - } - - public void parse() throws IOException { - if (stateListener == null) { - return; + if (splitKeyAndValue(line, splits)) { + key = splits[0]; + value = splits[1]; + appendMode = false; + } else { + if (key == null) { + // no key provided, we don't know where the value should be appended to + continue; } + // the rest value to previous key + value = line.trim(); + appendMode = true; + } - String line; - String[] splits = new String[2]; - String key = null; - String value = null; - boolean appendMode = false; - - NeoPackageInfo packageInfo = null; - - stateListener.onStartState(); - while ((line = reader.readLine()) != null) { - if (line.isEmpty()) { - continue; - } - - if (splitKeyAndValue(line, splits)) { - key = splits[0]; - value = splits[1]; - appendMode = false; - } else { - if (key == null) { - // no key provided, we don't know where the value should be appended to - continue; - } - // the rest value to previous key - value = line.trim(); - appendMode = true; - } - - if (key.equals(KEY_PACKAGE_NAME)) { - if (packageInfo != null) { - stateListener.onEndParsePackage(packageInfo); - } - packageInfo = stateListener.onCreatePackageInfo(); - packageInfo.setPackageName(value); - stateListener.onStartParsePackage(value, packageInfo); - } - - if (packageInfo == null) { - continue; - } - - if (appendMode) { - value = appendToLastValue(packageInfo, key, value); - } - - switch (key) { - case KEY_ARCH: - packageInfo.setArchitecture(Architecture.Companion.parse(value)); - break; - case KEY_DEPENDS: - packageInfo.setDependenciesString(value); - break; - case KEY_DESC: - packageInfo.setDescription(value); - break; - case KEY_ESSENTIAL: - packageInfo.setEssential(value.equals("yes")); - break; - case KEY_FILENAME: - packageInfo.setFileName(value); - break; - case KEY_HOMEPAGE: - packageInfo.setHomePage(value); - break; - case KEY_INSTALLED_SIZE: - packageInfo.setInstalledSizeInBytes(Long.parseLong(value)); - break; - case KEY_MAINTAINER: - packageInfo.setMaintainer(value); - break; - case KEY_MD5: - packageInfo.setMd5(value); - break; - case KEY_SHA1: - packageInfo.setSha1(value); - break; - case KEY_SHA256: - packageInfo.setSha256(value); - break; - case KEY_SIZE: - packageInfo.setSizeInBytes(Long.parseLong(value)); - break; - case KEY_VERSION: - packageInfo.setVersion(value); - break; - } - } + if (key.equals(KEY_PACKAGE_NAME)) { if (packageInfo != null) { - stateListener.onEndParsePackage(packageInfo); + stateListener.onEndParsePackage(packageInfo); } - stateListener.onEndState(); + packageInfo = stateListener.onCreatePackageInfo(); + packageInfo.setPackageName(value); + stateListener.onStartParsePackage(value, packageInfo); + } + + if (packageInfo == null) { + continue; + } + + if (appendMode) { + value = appendToLastValue(packageInfo, key, value); + } + + switch (key) { + case KEY_ARCH: + packageInfo.setArchitecture(Architecture.Companion.parse(value)); + break; + case KEY_DEPENDS: + packageInfo.setDependenciesString(value); + break; + case KEY_DESC: + packageInfo.setDescription(value); + break; + case KEY_ESSENTIAL: + packageInfo.setEssential(value.equals("yes")); + break; + case KEY_FILENAME: + packageInfo.setFileName(value); + break; + case KEY_HOMEPAGE: + packageInfo.setHomePage(value); + break; + case KEY_INSTALLED_SIZE: + packageInfo.setInstalledSizeInBytes(Long.parseLong(value)); + break; + case KEY_MAINTAINER: + packageInfo.setMaintainer(value); + break; + case KEY_MD5: + packageInfo.setMd5(value); + break; + case KEY_SHA1: + packageInfo.setSha1(value); + break; + case KEY_SHA256: + packageInfo.setSha256(value); + break; + case KEY_SIZE: + packageInfo.setSizeInBytes(Long.parseLong(value)); + break; + case KEY_VERSION: + packageInfo.setVersion(value); + break; + } + } + if (packageInfo != null) { + stateListener.onEndParsePackage(packageInfo); + } + stateListener.onEndState(); + } + + private String appendToLastValue(NeoPackageInfo packageInfo, String key, String value) { + // Currently, only descriptions can be multiline + switch (key) { + case KEY_DESC: + return packageInfo.getDescription() + " " + value; + default: + return value; + } + } + + private boolean splitKeyAndValue(String line, String[] splits) { + int valueIndex = line.indexOf(':'); + if (valueIndex < 0) { + return false; } - private String appendToLastValue(NeoPackageInfo packageInfo, String key, String value) { - // Currently, only descriptions can be multiline - switch (key) { - case KEY_DESC: - return packageInfo.getDescription() + " " + value; - default: - return value; - } - } - - private boolean splitKeyAndValue(String line, String[] splits) { - int valueIndex = line.indexOf(':'); - if (valueIndex < 0) { - return false; - } - - splits[0] = line.substring(0, valueIndex).trim(); - splits[1] = line.substring(valueIndex == line.length() ? valueIndex : valueIndex + 1).trim(); - return true; - } + splits[0] = line.substring(0, valueIndex).trim(); + splits[1] = line.substring(valueIndex == line.length() ? valueIndex : valueIndex + 1).trim(); + return true; + } } diff --git a/app/src/main/java/io/neoterm/component/pm/PackageComponent.java b/app/src/main/java/io/neoterm/component/pm/PackageComponent.java index 2693093..0d10fc6 100644 --- a/app/src/main/java/io/neoterm/component/pm/PackageComponent.java +++ b/app/src/main/java/io/neoterm/component/pm/PackageComponent.java @@ -1,120 +1,120 @@ package io.neoterm.component.pm; +import io.neoterm.frontend.component.NeoComponent; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.HashMap; -import io.neoterm.frontend.component.NeoComponent; - /** * @author kiva */ public class PackageComponent implements NeoComponent { - private final Object lock = new Object(); - private boolean isRefreshing = false; - private boolean queryEnabled = true; - private HashMap neoPackages; + private final Object lock = new Object(); + private boolean isRefreshing = false; + private boolean queryEnabled = true; + private HashMap neoPackages; - private NeoPackageInfo getPackageInfo(String packageName) { - return queryEnabled ? neoPackages.get(packageName) : null; + private NeoPackageInfo getPackageInfo(String packageName) { + return queryEnabled ? neoPackages.get(packageName) : null; + } + + public HashMap getPackages() { + return queryEnabled ? neoPackages : new HashMap<>(); + } + + public int getPackageCount() { + return queryEnabled ? neoPackages.size() : -1; + } + + public SourceManager getSourceManager() { + return new SourceManager(); + } + + public void reloadPackages(File packageListFile, boolean clearPrevious) throws IOException { + synchronized (lock) { + if (isRefreshing) { + return; + } + isRefreshing = true; } - - public HashMap getPackages() { - return queryEnabled ? neoPackages : new HashMap<>(); + tryParsePackages(packageListFile, clearPrevious); + synchronized (lock) { + isRefreshing = false; } + } - public int getPackageCount() { - return queryEnabled ? neoPackages.size() : -1; + public void clearPackages() { + if (isRefreshing) { + return; } + neoPackages.clear(); + } - public SourceManager getSourceManager() { - return new SourceManager(); - } - - public void reloadPackages(File packageListFile, boolean clearPrevious) throws IOException { - synchronized (lock) { - if (isRefreshing) { - return; - } - isRefreshing = true; + private void tryParsePackages(File packageListFile, final boolean clearPrevious) throws IOException { + NeoPackageParser packageParser = new NeoPackageParser(new FileInputStream(packageListFile)); + packageParser.setStateListener(new NeoPackageParser.ParseStateListener() { + @Override + public void onStartState() { + queryEnabled = false; + if (clearPrevious) { + neoPackages.clear(); } - tryParsePackages(packageListFile, clearPrevious); - synchronized (lock) { - isRefreshing = false; + } + + @Override + public void onEndState() { + queryEnabled = true; + for (NeoPackageInfo info : neoPackages.values()) { + resolveDepends(info); } + } + + @Override + public NeoPackageInfo onCreatePackageInfo() { + return new NeoPackageInfo(); + } + + @Override + public void onStartParsePackage(String name, NeoPackageInfo packageInfo) { + } + + @Override + public void onEndParsePackage(NeoPackageInfo packageInfo) { + neoPackages.put(packageInfo.getPackageName(), packageInfo); + } + }); + packageParser.parse(); + } + + private void resolveDepends(NeoPackageInfo info) { + String dep = info.getDependenciesString(); + if (dep == null) { + return; } - public void clearPackages() { - if (isRefreshing) { - return; - } - neoPackages.clear(); + String[] splits = dep.split(","); + NeoPackageInfo[] depends = new NeoPackageInfo[splits.length]; + info.setDependencies(depends); + + for (int i = 0; i < splits.length; ++i) { + String item = splits[i].trim(); + depends[i] = getPackageInfo(item); } + } - private void tryParsePackages(File packageListFile, final boolean clearPrevious) throws IOException { - NeoPackageParser packageParser = new NeoPackageParser(new FileInputStream(packageListFile)); - packageParser.setStateListener(new NeoPackageParser.ParseStateListener() { - @Override - public void onStartState() { - queryEnabled = false; - if (clearPrevious) { - neoPackages.clear(); - } - } + @Override + public void onServiceInit() { + neoPackages = new HashMap<>(); + } - @Override - public void onEndState() { - queryEnabled = true; - for (NeoPackageInfo info : neoPackages.values()) { - resolveDepends(info); - } - } + @Override + public void onServiceDestroy() { + } - @Override - public NeoPackageInfo onCreatePackageInfo() { - return new NeoPackageInfo(); - } - - @Override - public void onStartParsePackage(String name, NeoPackageInfo packageInfo) { - } - - @Override - public void onEndParsePackage(NeoPackageInfo packageInfo) { - neoPackages.put(packageInfo.getPackageName(), packageInfo); - } - }); - packageParser.parse(); - } - - private void resolveDepends(NeoPackageInfo info) { - String dep = info.getDependenciesString(); - if (dep == null) { - return; - } - - String[] splits = dep.split(","); - NeoPackageInfo[] depends = new NeoPackageInfo[splits.length]; - info.setDependencies(depends); - - for (int i = 0; i < splits.length; ++i) { - String item = splits[i].trim(); - depends[i] = getPackageInfo(item); - } - } - - @Override - public void onServiceInit() { - neoPackages = new HashMap<>(); - } - - @Override - public void onServiceDestroy() { - } - - @Override - public void onServiceObtained() { - } + @Override + public void onServiceObtained() { + } } diff --git a/app/src/main/java/io/neoterm/component/pm/Source.java b/app/src/main/java/io/neoterm/component/pm/Source.java index 68520be..e88f960 100644 --- a/app/src/main/java/io/neoterm/component/pm/Source.java +++ b/app/src/main/java/io/neoterm/component/pm/Source.java @@ -8,22 +8,22 @@ import io.neoterm.framework.database.annotation.Table; */ @Table public class Source { - @ID(autoIncrement = true) - private int id; + @ID(autoIncrement = true) + private int id; - public String url; + public String url; - public String repo; + public String repo; - public boolean enabled; + public boolean enabled; - public Source() { - // for Database - } + public Source() { + // for Database + } - public Source(String url, String repo, boolean enabled) { - this.url = url; - this.repo = repo; - this.enabled = enabled; - } + public Source(String url, String repo, boolean enabled) { + this.url = url; + this.repo = repo; + this.enabled = enabled; + } } diff --git a/app/src/main/java/io/neoterm/component/pm/SourceHelper.kt b/app/src/main/java/io/neoterm/component/pm/SourceHelper.kt index 1c9163a..bf9b429 100644 --- a/app/src/main/java/io/neoterm/component/pm/SourceHelper.kt +++ b/app/src/main/java/io/neoterm/component/pm/SourceHelper.kt @@ -12,63 +12,63 @@ import java.nio.file.Paths * @author kiva */ object SourceHelper { - fun syncSource() { - val sourceManager = ComponentManager.getComponent().sourceManager - syncSource(sourceManager) + fun syncSource() { + val sourceManager = ComponentManager.getComponent().sourceManager + syncSource(sourceManager) + } + + fun syncSource(sourceManager: SourceManager) { + val content = buildString { + this.append("# Generated by NeoTerm-Preference\n") + sourceManager.getEnabledSources() + .joinTo(this, "\n") { "deb [trusted=yes] ${it.url} ${it.repo}\n" } + } + kotlin.runCatching { + Files.write(Paths.get(NeoTermPath.SOURCE_FILE), content.toByteArray()) + } + } + + fun detectSourceFiles(): List { + val sourceManager = ComponentManager.getComponent().sourceManager + val sourceFiles = ArrayList() + try { + val prefixes = sourceManager.getEnabledSources() + .map { detectSourceFilePrefix(it) } + .filter { it.isNotEmpty() } + + File(NeoTermPath.PACKAGE_LIST_DIR) + .listFiles() + .filterTo(sourceFiles) { file -> + prefixes.filter { file.name.startsWith(it) } + .count() > 0 + } + } catch (e: Exception) { + sourceFiles.clear() + NLog.e("PM", "Failed to detect source files: ${e.localizedMessage}") } - fun syncSource(sourceManager: SourceManager) { - val content = buildString { - this.append("# Generated by NeoTerm-Preference\n") - sourceManager.getEnabledSources() - .joinTo(this, "\n") { "deb [trusted=yes] ${it.url} ${it.repo}\n" } - } - kotlin.runCatching { - Files.write(Paths.get(NeoTermPath.SOURCE_FILE), content.toByteArray()) - } - } - - fun detectSourceFiles(): List { - val sourceManager = ComponentManager.getComponent().sourceManager - val sourceFiles = ArrayList() - try { - val prefixes = sourceManager.getEnabledSources() - .map { detectSourceFilePrefix(it) } - .filter { it.isNotEmpty() } - - File(NeoTermPath.PACKAGE_LIST_DIR) - .listFiles() - .filterTo(sourceFiles) { file -> - prefixes.filter { file.name.startsWith(it) } - .count() > 0 - } - } catch (e: Exception) { - sourceFiles.clear() - NLog.e("PM", "Failed to detect source files: ${e.localizedMessage}") - } - - return sourceFiles - } - - fun detectSourceFilePrefix(source: Source): String { - try { - val url = URL(source.url) - val builder = StringBuilder(url.host) - if (url.port != -1) { - builder.append(":${url.port}") - } - - val path = url.path - if (path != null && path.isNotEmpty()) { - builder.append("_") - val fixedPath = path.replace("/", "_").substring(1) // skip the last '/' - builder.append(fixedPath) - } - builder.append("_dists_${source.repo.replace(" ".toRegex(), "_")}_binary-") - return builder.toString() - } catch (e: Exception) { - NLog.e("PM", "Failed to detect source file prefix: ${e.localizedMessage}") - return "" - } + return sourceFiles + } + + fun detectSourceFilePrefix(source: Source): String { + try { + val url = URL(source.url) + val builder = StringBuilder(url.host) + if (url.port != -1) { + builder.append(":${url.port}") + } + + val path = url.path + if (path != null && path.isNotEmpty()) { + builder.append("_") + val fixedPath = path.replace("/", "_").substring(1) // skip the last '/' + builder.append(fixedPath) + } + builder.append("_dists_${source.repo.replace(" ".toRegex(), "_")}_binary-") + return builder.toString() + } catch (e: Exception) { + NLog.e("PM", "Failed to detect source file prefix: ${e.localizedMessage}") + return "" } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/pm/SourceManager.kt b/app/src/main/java/io/neoterm/component/pm/SourceManager.kt index 270efb4..1c398cc 100644 --- a/app/src/main/java/io/neoterm/component/pm/SourceManager.kt +++ b/app/src/main/java/io/neoterm/component/pm/SourceManager.kt @@ -9,46 +9,46 @@ import io.neoterm.frontend.config.NeoTermPath * @author kiva */ class SourceManager internal constructor() { - private val database = NeoTermDatabase.instance("sources") + private val database = NeoTermDatabase.instance("sources") - init { - if (database.findAll(Source::class.java).isEmpty()) { - App.get().resources.getStringArray(R.array.pref_package_source_values) - .forEach { - database.saveBean(Source(it, "stable main", true)) - } + init { + if (database.findAll(Source::class.java).isEmpty()) { + App.get().resources.getStringArray(R.array.pref_package_source_values) + .forEach { + database.saveBean(Source(it, "stable main", true)) } } + } - fun addSource(sourceUrl: String, repo: String, enabled: Boolean) { - database.saveBean(Source(sourceUrl, repo, enabled)) - } + fun addSource(sourceUrl: String, repo: String, enabled: Boolean) { + database.saveBean(Source(sourceUrl, repo, enabled)) + } - fun removeSource(sourceUrl: String) { - database.deleteBeanByWhere(Source::class.java, "url == '$sourceUrl'") - } + fun removeSource(sourceUrl: String) { + database.deleteBeanByWhere(Source::class.java, "url == '$sourceUrl'") + } - fun updateAll(sources: List) { - database.dropAllTable() - database.saveBeans(sources) - } + fun updateAll(sources: List) { + database.dropAllTable() + database.saveBeans(sources) + } - fun getAllSources(): List { - return database.findAll(Source::class.java) - } + fun getAllSources(): List { + return database.findAll(Source::class.java) + } - fun getEnabledSources(): List { - return getAllSources().filter { it.enabled } - } + fun getEnabledSources(): List { + return getAllSources().filter { it.enabled } + } - fun getMainPackageSource(): String { - return getEnabledSources() - .map { it.repo } - .singleOrNull { it.trim() == "stable main" } - ?: NeoTermPath.DEFAULT_MAIN_PACKAGE_SOURCE - } + fun getMainPackageSource(): String { + return getEnabledSources() + .map { it.repo } + .singleOrNull { it.trim() == "stable main" } + ?: NeoTermPath.DEFAULT_MAIN_PACKAGE_SOURCE + } - fun applyChanges() { - database.vacuum() - } + fun applyChanges() { + database.vacuum() + } } diff --git a/app/src/main/java/io/neoterm/component/profile/NeoProfile.kt b/app/src/main/java/io/neoterm/component/profile/NeoProfile.kt index 964fba3..88e21c4 100644 --- a/app/src/main/java/io/neoterm/component/profile/NeoProfile.kt +++ b/app/src/main/java/io/neoterm/component/profile/NeoProfile.kt @@ -17,57 +17,57 @@ import java.io.File * @author kiva */ abstract class NeoProfile : CodeGenObject, ConfigFileBasedObject { - companion object { - private const val PROFILE_NAME = "name" + companion object { + private const val PROFILE_NAME = "name" + } + + abstract val profileMetaName: String + private val profileMetaPath + get() = arrayOf(profileMetaName) + + var profileName = "Unknown Profile" + + override fun onConfigLoaded(configVisitor: ConfigVisitor) { + profileName = configVisitor.getProfileString(PROFILE_NAME, profileName) + } + + override fun getCodeGenerator(parameter: CodeGenParameter): CodeGenerator { + return NeoProfileGenerator(parameter) + } + + @TestOnly + fun testLoadConfigure(file: File): Boolean { + val loaderService = ComponentManager.getComponent() + + val configure: NeoConfigureFile? + try { + configure = loaderService.newLoader(file).loadConfigure() + if (configure == null) { + throw RuntimeException("Parse configuration failed.") + } + } catch (e: Exception) { + NLog.e("Profile", "Failed to load profile: ${file.absolutePath}: ${e.localizedMessage}") + return false } - abstract val profileMetaName: String - private val profileMetaPath - get() = arrayOf(profileMetaName) + val visitor = configure.getVisitor() + onConfigLoaded(visitor) + return true + } - var profileName = "Unknown Profile" + protected fun ConfigVisitor.getProfileString(key: String, fallback: String): String { + return getProfileString(key) ?: fallback + } - override fun onConfigLoaded(configVisitor: ConfigVisitor) { - profileName = configVisitor.getProfileString(PROFILE_NAME, profileName) - } + protected fun ConfigVisitor.getProfileBoolean(key: String, fallback: Boolean): Boolean { + return getProfileBoolean(key) ?: fallback + } - override fun getCodeGenerator(parameter: CodeGenParameter): CodeGenerator { - return NeoProfileGenerator(parameter) - } + protected fun ConfigVisitor.getProfileString(key: String): String? { + return this.getStringValue(profileMetaPath, key) + } - @TestOnly - fun testLoadConfigure(file: File): Boolean { - val loaderService = ComponentManager.getComponent() - - val configure: NeoConfigureFile? - try { - configure = loaderService.newLoader(file).loadConfigure() - if (configure == null) { - throw RuntimeException("Parse configuration failed.") - } - } catch (e: Exception) { - NLog.e("Profile", "Failed to load profile: ${file.absolutePath}: ${e.localizedMessage}") - return false - } - - val visitor = configure.getVisitor() - onConfigLoaded(visitor) - return true - } - - protected fun ConfigVisitor.getProfileString(key: String, fallback: String): String { - return getProfileString(key) ?: fallback - } - - protected fun ConfigVisitor.getProfileBoolean(key: String, fallback: Boolean): Boolean { - return getProfileBoolean(key) ?: fallback - } - - protected fun ConfigVisitor.getProfileString(key: String): String? { - return this.getStringValue(profileMetaPath, key) - } - - protected fun ConfigVisitor.getProfileBoolean(key: String): Boolean? { - return this.getBooleanValue(profileMetaPath, key) - } + protected fun ConfigVisitor.getProfileBoolean(key: String): Boolean? { + return this.getBooleanValue(profileMetaPath, key) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/profile/ProfileComponent.kt b/app/src/main/java/io/neoterm/component/profile/ProfileComponent.kt index df38c33..f362413 100644 --- a/app/src/main/java/io/neoterm/component/profile/ProfileComponent.kt +++ b/app/src/main/java/io/neoterm/component/profile/ProfileComponent.kt @@ -10,57 +10,57 @@ import java.io.File * @author kiva */ class ProfileComponent : ConfigFileBasedComponent(NeoTermPath.PROFILE_PATH) { - override val checkComponentFileWhenObtained - get() = true + override val checkComponentFileWhenObtained + get() = true - private val profileRegistry = mutableMapOf>() - private val profileList = mutableMapOf>() + private val profileRegistry = mutableMapOf>() + private val profileList = mutableMapOf>() - override fun onCheckComponentFiles() = reloadProfiles() + override fun onCheckComponentFiles() = reloadProfiles() - override fun onCreateComponentObject(configVisitor: ConfigVisitor): NeoProfile { - val rootContext = configVisitor.getRootContext() + override fun onCreateComponentObject(configVisitor: ConfigVisitor): NeoProfile { + val rootContext = configVisitor.getRootContext() - val profileClass = rootContext.children - .mapNotNull { - profileRegistry[it.contextName] - } - .singleOrNull() + val profileClass = rootContext.children + .mapNotNull { + profileRegistry[it.contextName] + } + .singleOrNull() - if (profileClass != null) { - NLog.e("ProfileComponent", "Loaded profile: " + profileClass.name) - return profileClass.newInstance() + if (profileClass != null) { + NLog.e("ProfileComponent", "Loaded profile: " + profileClass.name) + return profileClass.newInstance() + } + + throw IllegalArgumentException("No proper profile registry found") + } + + fun getProfiles(metaName: String): List = profileList[metaName] ?: listOf() + + fun reloadProfiles() { + profileList.clear() + File(baseDir) + .listFiles(NEOLANG_FILTER) + .mapNotNull { + this.loadConfigure(it) + } + .forEach { + val list = profileList[it.profileMetaName] + if (list != null) { + list.add(it) + } else { + val newList = mutableListOf(it) + profileList.put(it.profileMetaName, newList) } + } + } - throw IllegalArgumentException("No proper profile registry found") - } + fun registerProfile(metaName: String, prototype: Class) { + profileRegistry[metaName] = prototype + reloadProfiles() + } - fun getProfiles(metaName: String): List = profileList[metaName] ?: listOf() - - fun reloadProfiles() { - profileList.clear() - File(baseDir) - .listFiles(NEOLANG_FILTER) - .mapNotNull { - this.loadConfigure(it) - } - .forEach { - val list = profileList[it.profileMetaName] - if (list != null) { - list.add(it) - } else { - val newList = mutableListOf(it) - profileList.put(it.profileMetaName, newList) - } - } - } - - fun registerProfile(metaName: String, prototype: Class) { - profileRegistry[metaName] = prototype - reloadProfiles() - } - - fun unregisterProfile(metaName: String) { - profileRegistry.remove(metaName) - } + fun unregisterProfile(metaName: String) { + profileRegistry.remove(metaName) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/session/SessionComponent.kt b/app/src/main/java/io/neoterm/component/session/SessionComponent.kt index de18134..2fa9b89 100644 --- a/app/src/main/java/io/neoterm/component/session/SessionComponent.kt +++ b/app/src/main/java/io/neoterm/component/session/SessionComponent.kt @@ -1,8 +1,8 @@ package io.neoterm.component.session import android.annotation.SuppressLint -import androidx.appcompat.app.AppCompatActivity import android.content.Context +import androidx.appcompat.app.AppCompatActivity import io.neoterm.Globals import io.neoterm.frontend.component.NeoComponent import io.neoterm.frontend.config.NeoTermPath @@ -17,94 +17,96 @@ import io.neoterm.frontend.session.xorg.client.XSessionData * @author kiva */ class SessionComponent : NeoComponent { - companion object { - private var IS_LIBRARIES_LOADED = false + companion object { + private var IS_LIBRARIES_LOADED = false - private fun wrapLibraryName(libName: String): String { - return "lib$libName.so" + private fun wrapLibraryName(libName: String): String { + return "lib$libName.so" + } + + @SuppressLint("UnsafeDynamicallyLoadedCode") + private fun loadLibraries(): Boolean { + try { + if (Globals.NeedGles3) { + System.loadLibrary("GLESv3") + NLog.e("SessionComponent", "Loaded GLESv3 lib") + } else if (Globals.NeedGles2) { + System.loadLibrary("GLESv2") + NLog.e("SessionComponent", "Loaded GLESv2 lib") } + } catch (e: UnsatisfiedLinkError) { + NLog.e("SessionComponent", "Cannot load GLESv3 or GLESv2 lib") + } - @SuppressLint("UnsafeDynamicallyLoadedCode") - private fun loadLibraries(): Boolean { + var result: Boolean + try { + Globals.XLIBS + .plus(Globals.XAPP_LIBS) + .forEach { + val soPath = "${NeoTermPath.LIB_PATH}/xorg-neoterm/${wrapLibraryName(it)}" + NLog.e("SessionComponent", "Loading lib " + soPath) try { - if (Globals.NeedGles3) { - System.loadLibrary("GLESv3") - NLog.e("SessionComponent", "Loaded GLESv3 lib") - } else if (Globals.NeedGles2) { - System.loadLibrary("GLESv2") - NLog.e("SessionComponent", "Loaded GLESv2 lib") - } - } catch (e: UnsatisfiedLinkError) { - NLog.e("SessionComponent", "Cannot load GLESv3 or GLESv2 lib") + System.load(soPath) + } catch (error: UnsatisfiedLinkError) { + NLog.e( + "SessionComponent", "Error loading lib " + soPath + + ", reason: " + error.localizedMessage + ) + result = false } + } + result = true - var result: Boolean - try { - Globals.XLIBS - .plus(Globals.XAPP_LIBS) - .forEach { - val soPath = "${NeoTermPath.LIB_PATH}/xorg-neoterm/${wrapLibraryName(it)}" - NLog.e("SessionComponent", "Loading lib " + soPath) - try { - System.load(soPath) - } catch (error: UnsatisfiedLinkError) { - NLog.e("SessionComponent", "Error loading lib " + soPath - + ", reason: " + error.localizedMessage) - result = false - } - } - result = true + } catch (ignore: UnsatisfiedLinkError) { + NLog.e("SessionComponent", ignore.localizedMessage) + result = false + } - } catch (ignore: UnsatisfiedLinkError) { - NLog.e("SessionComponent", ignore.localizedMessage) - result = false - } + return result + } - return result - } - - private fun checkLibrariesLoaded(): Boolean { - if (!IS_LIBRARIES_LOADED) { - synchronized(SessionComponent::class.java) { - if (!IS_LIBRARIES_LOADED) { - IS_LIBRARIES_LOADED = loadLibraries() - } - } - } - return IS_LIBRARIES_LOADED + private fun checkLibrariesLoaded(): Boolean { + if (!IS_LIBRARIES_LOADED) { + synchronized(SessionComponent::class.java) { + if (!IS_LIBRARIES_LOADED) { + IS_LIBRARIES_LOADED = loadLibraries() + } } + } + return IS_LIBRARIES_LOADED } + } - override fun onServiceInit() { + override fun onServiceInit() { + } + + override fun onServiceDestroy() { + } + + override fun onServiceObtained() { + } + + fun createSession(context: Context, parameter: XParameter): XSession { + if (context is AppCompatActivity) { + if (!checkLibrariesLoaded()) { + throw RuntimeException("Cannot load libraries!") + } + + return XSession(context, XSessionData()) } + throw RuntimeException("Creating X sessions requires Activity, but got Context") + } - override fun onServiceDestroy() { - } - - override fun onServiceObtained() { - } - - fun createSession(context: Context, parameter: XParameter): XSession { - if (context is AppCompatActivity) { - if (!checkLibrariesLoaded()) { - throw RuntimeException("Cannot load libraries!") - } - - return XSession(context, XSessionData()) - } - throw RuntimeException("Creating X sessions requires Activity, but got Context") - } - - fun createSession(context: Context, parameter: ShellParameter): ShellTermSession { - return ShellTermSession.Builder() - .executablePath(parameter.executablePath) - .currentWorkingDirectory(parameter.cwd) - .callback(parameter.sessionCallback) - .systemShell(parameter.systemShell) - .envArray(parameter.env) - .argArray(parameter.arguments) - .initialCommand(parameter.initialCommand) - .profile(parameter.shellProfile) - .create(context) - } + fun createSession(context: Context, parameter: ShellParameter): ShellTermSession { + return ShellTermSession.Builder() + .executablePath(parameter.executablePath) + .currentWorkingDirectory(parameter.cwd) + .callback(parameter.sessionCallback) + .systemShell(parameter.systemShell) + .envArray(parameter.env) + .argArray(parameter.arguments) + .initialCommand(parameter.initialCommand) + .profile(parameter.shellProfile) + .create(context) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/component/userscript/UserScriptComponent.kt b/app/src/main/java/io/neoterm/component/userscript/UserScriptComponent.kt index a3ae779..9cee684 100644 --- a/app/src/main/java/io/neoterm/component/userscript/UserScriptComponent.kt +++ b/app/src/main/java/io/neoterm/component/userscript/UserScriptComponent.kt @@ -13,48 +13,48 @@ import java.io.File * @author kiva */ class UserScriptComponent : NeoComponent { - lateinit var userScripts: MutableList + lateinit var userScripts: MutableList - override fun onServiceInit() { - checkForFiles() - } + override fun onServiceInit() { + checkForFiles() + } - override fun onServiceDestroy() { - } + override fun onServiceDestroy() { + } - override fun onServiceObtained() { - checkForFiles() - } + override fun onServiceObtained() { + checkForFiles() + } - private fun extractDefaultScript(context: Context): Boolean { - try { - AssetsUtils.extractAssetsDir(context, "scripts", NeoTermPath.USER_SCRIPT_PATH) - File(NeoTermPath.USER_SCRIPT_PATH) - .listFiles().forEach { - Os.chmod(it.absolutePath, 448 /*Dec of 0700*/) - } - return true - } catch (e: Exception) { - NLog.e("UserScript", "Failed to extract default user scripts: ${e.localizedMessage}") - return false + private fun extractDefaultScript(context: Context): Boolean { + try { + AssetsUtils.extractAssetsDir(context, "scripts", NeoTermPath.USER_SCRIPT_PATH) + File(NeoTermPath.USER_SCRIPT_PATH) + .listFiles().forEach { + Os.chmod(it.absolutePath, 448 /*Dec of 0700*/) } + return true + } catch (e: Exception) { + NLog.e("UserScript", "Failed to extract default user scripts: ${e.localizedMessage}") + return false } + } - private fun checkForFiles() { - File(NeoTermPath.USER_SCRIPT_PATH).mkdirs() - userScripts = mutableListOf() + private fun checkForFiles() { + File(NeoTermPath.USER_SCRIPT_PATH).mkdirs() + userScripts = mutableListOf() - extractDefaultScript(App.get()) - reloadScripts() - } + extractDefaultScript(App.get()) + reloadScripts() + } - fun reloadScripts() { - val userScriptDir = File(NeoTermPath.USER_SCRIPT_PATH) - userScriptDir.mkdirs() + fun reloadScripts() { + val userScriptDir = File(NeoTermPath.USER_SCRIPT_PATH) + userScriptDir.mkdirs() - userScripts.clear() - userScriptDir.listFiles() - .takeWhile { it.canExecute() } - .mapTo(userScripts, { UserScript(it) }) - } + userScripts.clear() + userScriptDir.listFiles() + .takeWhile { it.canExecute() } + .mapTo(userScripts, { UserScript(it) }) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/framework/NeoTermDatabase.java b/app/src/main/java/io/neoterm/framework/NeoTermDatabase.java index f259f09..a512db4 100644 --- a/app/src/main/java/io/neoterm/framework/NeoTermDatabase.java +++ b/app/src/main/java/io/neoterm/framework/NeoTermDatabase.java @@ -5,671 +5,660 @@ import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import io.neoterm.App; +import io.neoterm.framework.database.*; +import io.neoterm.framework.database.bean.TableInfo; +import io.neoterm.framework.reflection.Reflect; +import io.neoterm.frontend.logging.NLog; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import io.neoterm.App; -import io.neoterm.framework.database.DatabaseDataType; -import io.neoterm.framework.database.OnDatabaseUpgradedListener; -import io.neoterm.framework.database.SQLStatementHelper; -import io.neoterm.framework.database.SQLTypeParser; -import io.neoterm.framework.database.NeoTermSQLiteConfig; -import io.neoterm.framework.database.TableHelper; -import io.neoterm.framework.database.ValueHelper; -import io.neoterm.framework.database.bean.TableInfo; -import io.neoterm.framework.reflection.Reflect; -import io.neoterm.frontend.logging.NLog; +import java.util.*; /** * @author Lody, Kiva - *

- * 基于DTO(DataToObject)映射的数据库操纵模型. - * 通过少量可选的注解,即可构造数据模型. - * 增删查改异常轻松. + *

+ * 基于DTO(DataToObject)映射的数据库操纵模型. + * 通过少量可选的注解,即可构造数据模型. + * 增删查改异常轻松. * @version 1.4 */ public class NeoTermDatabase { - /** - * 缓存创建的数据库,以便防止数据库冲突. - */ - private static final Map DAO_MAP = new HashMap<>(); + /** + * 缓存创建的数据库,以便防止数据库冲突. + */ + private static final Map DAO_MAP = new HashMap<>(); - /** - * 数据库配置 - */ - private NeoTermSQLiteConfig neoTermSQLiteConfig; - /** - * 内部操纵的数据库执行类 - */ - private SQLiteDatabase db; + /** + * 数据库配置 + */ + private NeoTermSQLiteConfig neoTermSQLiteConfig; + /** + * 内部操纵的数据库执行类 + */ + private SQLiteDatabase db; - /** - * 默认构造器 - * - * @param config - */ - private NeoTermDatabase(NeoTermSQLiteConfig config) { + /** + * 默认构造器 + * + * @param config + */ + private NeoTermDatabase(NeoTermSQLiteConfig config) { - this.neoTermSQLiteConfig = config; - String saveDir = config.getSaveDir(); - if (saveDir != null - && saveDir.trim().length() > 0) { - this.db = createDataBaseFileOnSDCard(saveDir, - config.getDatabaseName()); - } else { - this.db = new SQLiteDataBaseHelper(App.Companion.get() - .getApplicationContext() - .getApplicationContext(), config) - .getWritableDatabase(); + this.neoTermSQLiteConfig = config; + String saveDir = config.getSaveDir(); + if (saveDir != null + && saveDir.trim().length() > 0) { + this.db = createDataBaseFileOnSDCard(saveDir, + config.getDatabaseName()); + } else { + this.db = new SQLiteDataBaseHelper(App.Companion.get() + .getApplicationContext() + .getApplicationContext(), config) + .getWritableDatabase(); + } + + } + + /** + * 根据配置取得用于操纵数据库的WeLikeDao实例 + * + * @param config + * @return + */ + public static NeoTermDatabase instance(NeoTermSQLiteConfig config) { + if (config.getDatabaseName() == null) { + throw new IllegalArgumentException("DBName is null in SqLiteConfig."); + } + NeoTermDatabase dao = DAO_MAP.get(config.getDatabaseName()); + if (dao == null) { + dao = new NeoTermDatabase(config); + synchronized (DAO_MAP) { + DAO_MAP.put(config.getDatabaseName(), dao); + } + } else {//更换配置 + dao.applyConfig(config); + } + + return dao; + } + + /** + * 根据默认配置取得操纵数据库的WeLikeDao实例 + * + * @return + */ + public static NeoTermDatabase instance() { + return instance(NeoTermSQLiteConfig.DEFAULT_CONFIG); + } + + /** + * 取得操纵数据库的WeLikeDao实例 + * + * @param dbName + * @return + */ + public static NeoTermDatabase instance(String dbName) { + NeoTermSQLiteConfig config = new NeoTermSQLiteConfig(); + config.setDatabaseName(dbName); + return instance(config); + } + + /** + * 取得操纵数据库的WeLikeDao实例 + * + * @param dbVersion + * @return + */ + public static NeoTermDatabase instance(int dbVersion) { + NeoTermSQLiteConfig config = new NeoTermSQLiteConfig(); + config.setDatabaseVersion(dbVersion); + return instance(config); + } + + /** + * 取得操纵数据库的WeLikeDao实例 + * + * @param listener + * @return + */ + public static NeoTermDatabase instance(OnDatabaseUpgradedListener listener) { + NeoTermSQLiteConfig config = new NeoTermSQLiteConfig(); + config.setOnDatabaseUpgradedListener(listener); + return instance(config); + } + + /** + * 取得操纵数据库的WeLikeDao实例 + * + * @param dbName + * @param dbVersion + * @return + */ + public static NeoTermDatabase instance(String dbName, int dbVersion) { + NeoTermSQLiteConfig config = new NeoTermSQLiteConfig(); + config.setDatabaseName(dbName); + config.setDatabaseVersion(dbVersion); + return instance(config); + } + + /** + * 取得操纵数据库的WeLikeDao实例 + * + * @param dbName + * @param dbVersion + * @param listener + * @return + */ + public static NeoTermDatabase instance(String dbName, int dbVersion, OnDatabaseUpgradedListener listener) { + NeoTermSQLiteConfig config = new NeoTermSQLiteConfig(); + config.setDatabaseName(dbName); + config.setDatabaseVersion(dbVersion); + config.setOnDatabaseUpgradedListener(listener); + return instance(config); + } + + /** + * 配置为新的参数(不改变数据库名). + * + * @param config + */ + private void applyConfig(NeoTermSQLiteConfig config) { + this.neoTermSQLiteConfig.debugMode = config.debugMode; + this.neoTermSQLiteConfig.setOnDatabaseUpgradedListener(config.getOnDatabaseUpgradedListener()); + } + + public void release() { + DAO_MAP.clear(); + if (neoTermSQLiteConfig.debugMode) { + NLog.INSTANCE.d("缓存的DAO已经全部清除,将不占用内存."); + } + } + + + /** + * 在SD卡的指定目录上创建数据库文件 + * + * @param sdcardPath sd卡路径 + * @param dbFileName 数据库文件名 + * @return + */ + private SQLiteDatabase createDataBaseFileOnSDCard(String sdcardPath, + String dbFileName) { + File dbFile = new File(sdcardPath, dbFileName); + if (!dbFile.exists()) { + try { + if (dbFile.createNewFile()) { + return SQLiteDatabase.openOrCreateDatabase(dbFile, null); } - + } catch (IOException e) { + throw new RuntimeException("无法在 " + dbFile.getAbsolutePath() + "创建DB文件."); + } + } else { + //数据库文件已经存在,无需再次创建. + return SQLiteDatabase.openOrCreateDatabase(dbFile, null); } + return null; + } - /** - * 根据配置取得用于操纵数据库的WeLikeDao实例 - * - * @param config - * @return - */ - public static NeoTermDatabase instance(NeoTermSQLiteConfig config) { - if (config.getDatabaseName() == null) { - throw new IllegalArgumentException("DBName is null in SqLiteConfig."); - } - NeoTermDatabase dao = DAO_MAP.get(config.getDatabaseName()); - if (dao == null) { - dao = new NeoTermDatabase(config); - synchronized (DAO_MAP) { - DAO_MAP.put(config.getDatabaseName(), dao); - } - } else {//更换配置 - dao.applyConfig(config); - } - - return dao; + /** + * 如果表不存在,需要创建它. + * + * @param clazz + */ + private void createTableIfNeed(Class clazz) { + TableInfo tableInfo = TableHelper.from(clazz); + if (tableInfo.isCreate) { + return; } - - /** - * 根据默认配置取得操纵数据库的WeLikeDao实例 - * - * @return - */ - public static NeoTermDatabase instance() { - return instance(NeoTermSQLiteConfig.DEFAULT_CONFIG); - } - - /** - * 取得操纵数据库的WeLikeDao实例 - * - * @param dbName - * @return - */ - public static NeoTermDatabase instance(String dbName) { - NeoTermSQLiteConfig config = new NeoTermSQLiteConfig(); - config.setDatabaseName(dbName); - return instance(config); - } - - /** - * 取得操纵数据库的WeLikeDao实例 - * - * @param dbVersion - * @return - */ - public static NeoTermDatabase instance(int dbVersion) { - NeoTermSQLiteConfig config = new NeoTermSQLiteConfig(); - config.setDatabaseVersion(dbVersion); - return instance(config); - } - - /** - * 取得操纵数据库的WeLikeDao实例 - * - * @param listener - * @return - */ - public static NeoTermDatabase instance(OnDatabaseUpgradedListener listener) { - NeoTermSQLiteConfig config = new NeoTermSQLiteConfig(); - config.setOnDatabaseUpgradedListener(listener); - return instance(config); - } - - /** - * 取得操纵数据库的WeLikeDao实例 - * - * @param dbName - * @param dbVersion - * @return - */ - public static NeoTermDatabase instance(String dbName, int dbVersion) { - NeoTermSQLiteConfig config = new NeoTermSQLiteConfig(); - config.setDatabaseName(dbName); - config.setDatabaseVersion(dbVersion); - return instance(config); - } - - /** - * 取得操纵数据库的WeLikeDao实例 - * - * @param dbName - * @param dbVersion - * @param listener - * @return - */ - public static NeoTermDatabase instance(String dbName, int dbVersion, OnDatabaseUpgradedListener listener) { - NeoTermSQLiteConfig config = new NeoTermSQLiteConfig(); - config.setDatabaseName(dbName); - config.setDatabaseVersion(dbVersion); - config.setOnDatabaseUpgradedListener(listener); - return instance(config); - } - - /** - * 配置为新的参数(不改变数据库名). - * - * @param config - */ - private void applyConfig(NeoTermSQLiteConfig config) { - this.neoTermSQLiteConfig.debugMode = config.debugMode; - this.neoTermSQLiteConfig.setOnDatabaseUpgradedListener(config.getOnDatabaseUpgradedListener()); - } - - public void release() { - DAO_MAP.clear(); - if (neoTermSQLiteConfig.debugMode) { - NLog.INSTANCE.d("缓存的DAO已经全部清除,将不占用内存."); - } - } - - - /** - * 在SD卡的指定目录上创建数据库文件 - * - * @param sdcardPath sd卡路径 - * @param dbFileName 数据库文件名 - * @return - */ - private SQLiteDatabase createDataBaseFileOnSDCard(String sdcardPath, - String dbFileName) { - File dbFile = new File(sdcardPath, dbFileName); - if (!dbFile.exists()) { - try { - if (dbFile.createNewFile()) { - return SQLiteDatabase.openOrCreateDatabase(dbFile, null); - } - } catch (IOException e) { - throw new RuntimeException("无法在 " + dbFile.getAbsolutePath() + "创建DB文件."); - } - } else { - //数据库文件已经存在,无需再次创建. - return SQLiteDatabase.openOrCreateDatabase(dbFile, null); - } - return null; - } - - /** - * 如果表不存在,需要创建它. - * - * @param clazz - */ - private void createTableIfNeed(Class clazz) { - TableInfo tableInfo = TableHelper.from(clazz); - if (tableInfo.isCreate) { - return; - } - if (!isTableExist(tableInfo)) { - String sql = SQLStatementHelper.createTable(tableInfo); - if (neoTermSQLiteConfig.debugMode) { - NLog.INSTANCE.w(sql); - } - db.execSQL(sql); - Method afterTableCreateMethod = tableInfo.afterTableCreateMethod; - if (afterTableCreateMethod != null) { - //如果afterTableMethod存在,就调用它 - try { - afterTableCreateMethod.invoke(null, this); - } catch (Throwable ignore) { - ignore.printStackTrace(); - } - } - } - } - - /** - * 判断表是否存在? - * - * @param table 需要盘的的表 - * @return - */ - private boolean isTableExist(TableInfo table) { - String sql = "SELECT COUNT(*) AS c FROM sqlite_master WHERE type ='table' AND name ='" - + table.tableName + "' "; - try (Cursor cursor = db.rawQuery(sql, null)) { - if (cursor != null && cursor.moveToNext()) { - int count = cursor.getInt(0); - if (count > 0) { - return true; - } - } + if (!isTableExist(tableInfo)) { + String sql = SQLStatementHelper.createTable(tableInfo); + if (neoTermSQLiteConfig.debugMode) { + NLog.INSTANCE.w(sql); + } + db.execSQL(sql); + Method afterTableCreateMethod = tableInfo.afterTableCreateMethod; + if (afterTableCreateMethod != null) { + //如果afterTableMethod存在,就调用它 + try { + afterTableCreateMethod.invoke(null, this); } catch (Throwable ignore) { + ignore.printStackTrace(); + } + } + } + } + + /** + * 判断表是否存在? + * + * @param table 需要盘的的表 + * @return + */ + private boolean isTableExist(TableInfo table) { + String sql = "SELECT COUNT(*) AS c FROM sqlite_master WHERE type ='table' AND name ='" + + table.tableName + "' "; + try (Cursor cursor = db.rawQuery(sql, null)) { + if (cursor != null && cursor.moveToNext()) { + int count = cursor.getInt(0); + if (count > 0) { + return true; + } + } + } catch (Throwable ignore) { + ignore.printStackTrace(); + } + + return false; + } + + /** + * 删除全部的表 + */ + public void dropAllTable() { + try (Cursor cursor = db.rawQuery( + "SELECT name FROM sqlite_master WHERE type ='table'", null)) { + if (cursor != null) { + cursor.moveToFirst(); + while (cursor.moveToNext()) { + try { + dropTable(cursor.getString(0)); + } catch (SQLException ignore) { ignore.printStackTrace(); + } } + } + } + } - return false; + /** + * 取得数据库中的表的数量 + * + * @return 表的数量 + */ + public int tableCount() { + try (Cursor cursor = db.rawQuery( + "SELECT name FROM sqlite_master WHERE type ='table'", null)) { + return cursor == null ? 0 : cursor.getCount(); + } + } + + /** + * 取得数据库中的所有表名组成的List. + * + * @return + */ + public List getTableList() { + try (Cursor cursor = db.rawQuery( + "SELECT name FROM sqlite_master WHERE type ='table'", null)) { + List tableList = new ArrayList<>(); + if (cursor != null) { + cursor.moveToFirst(); + while (cursor.moveToNext()) { + tableList.add(cursor.getString(0)); + } + } + return tableList; + } + } + + /** + * 删除一张表 + * + * @param beanClass 表所对应的类 + */ + public void dropTable(Class beanClass) { + TableInfo tableInfo = TableHelper.from(beanClass); + dropTable(tableInfo.tableName); + tableInfo.isCreate = false; + } + + /** + * 删除一张表 + * + * @param tableName 表名 + */ + public void dropTable(String tableName) { + String statement = "DROP TABLE IF EXISTS " + tableName; + if (neoTermSQLiteConfig.debugMode) { + NLog.INSTANCE.w(statement); + } + db.execSQL(statement); + TableInfo tableInfo = TableHelper.findTableInfoByName(tableName); + if (tableInfo != null) { + tableInfo.isCreate = false; + } + } + + /** + * 存储一个Bean. + * + * @param bean + * @return + */ + public NeoTermDatabase saveBean(T bean) { + createTableIfNeed(bean.getClass()); + String statement = SQLStatementHelper.insertIntoTable(bean); + if (neoTermSQLiteConfig.debugMode) { + NLog.INSTANCE.w(statement); + } + db.execSQL(statement); + return this; + + } + + /** + * 存储多个Bean. + * + * @param beans + * @return + */ + public NeoTermDatabase saveBeans(Object[] beans) { + for (Object o : beans) { + saveBean(o); } - /** - * 删除全部的表 - */ - public void dropAllTable() { - try (Cursor cursor = db.rawQuery( - "SELECT name FROM sqlite_master WHERE type ='table'", null)) { - if (cursor != null) { - cursor.moveToFirst(); - while (cursor.moveToNext()) { - try { - dropTable(cursor.getString(0)); - } catch (SQLException ignore) { - ignore.printStackTrace(); - } - } - } - } + return this; + } + + /** + * 存储多个Bean. + * + * @param beans + * @return + */ + public NeoTermDatabase saveBeans(List beans) { + for (Object o : beans) { + saveBean(o); } - /** - * 取得数据库中的表的数量 - * - * @return 表的数量 - */ - public int tableCount() { - try (Cursor cursor = db.rawQuery( - "SELECT name FROM sqlite_master WHERE type ='table'", null)) { - return cursor == null ? 0 : cursor.getCount(); - } + return this; + } + + /** + * 寻找Bean对应的全部数据 + * + * @param clazz + * @param + * @return + */ + public List findAll(Class clazz) { + createTableIfNeed(clazz); + TableInfo tableInfo = TableHelper.from(clazz); + String statement = SQLStatementHelper.selectTable(tableInfo.tableName); + if (neoTermSQLiteConfig.debugMode) { + NLog.INSTANCE.w(statement); } + List list = new ArrayList(); + try (Cursor cursor = db.rawQuery(statement, null)) { + if (cursor == null) { + // DO NOT RETURN NULL + // null checks are ugly! + return Collections.emptyList(); + } - /** - * 取得数据库中的所有表名组成的List. - * - * @return - */ - public List getTableList() { - try (Cursor cursor = db.rawQuery( - "SELECT name FROM sqlite_master WHERE type ='table'", null)) { - List tableList = new ArrayList<>(); - if (cursor != null) { - cursor.moveToFirst(); - while (cursor.moveToNext()) { - tableList.add(cursor.getString(0)); - } - } - return tableList; - } - } + while (cursor.moveToNext()) { + T object = Reflect.on(clazz).create().get(); - /** - * 删除一张表 - * - * @param beanClass 表所对应的类 - */ - public void dropTable(Class beanClass) { - TableInfo tableInfo = TableHelper.from(beanClass); - dropTable(tableInfo.tableName); - tableInfo.isCreate = false; - } - - /** - * 删除一张表 - * - * @param tableName 表名 - */ - public void dropTable(String tableName) { - String statement = "DROP TABLE IF EXISTS " + tableName; - if (neoTermSQLiteConfig.debugMode) { - NLog.INSTANCE.w(statement); - } - db.execSQL(statement); - TableInfo tableInfo = TableHelper.findTableInfoByName(tableName); - if (tableInfo != null) { - tableInfo.isCreate = false; - } - } - - /** - * 存储一个Bean. - * - * @param bean - * @return - */ - public NeoTermDatabase saveBean(T bean) { - createTableIfNeed(bean.getClass()); - String statement = SQLStatementHelper.insertIntoTable(bean); - if (neoTermSQLiteConfig.debugMode) { - NLog.INSTANCE.w(statement); - } - db.execSQL(statement); - return this; - - } - - /** - * 存储多个Bean. - * - * @param beans - * @return - */ - public NeoTermDatabase saveBeans(Object[] beans) { - for (Object o : beans) { - saveBean(o); - } - - return this; - } - - /** - * 存储多个Bean. - * - * @param beans - * @return - */ - public NeoTermDatabase saveBeans(List beans) { - for (Object o : beans) { - saveBean(o); - } - - return this; - } - - /** - * 寻找Bean对应的全部数据 - * - * @param clazz - * @param - * @return - */ - public List findAll(Class clazz) { - createTableIfNeed(clazz); - TableInfo tableInfo = TableHelper.from(clazz); - String statement = SQLStatementHelper.selectTable(tableInfo.tableName); - if (neoTermSQLiteConfig.debugMode) { - NLog.INSTANCE.w(statement); - } - List list = new ArrayList(); - try (Cursor cursor = db.rawQuery(statement, null)) { - if (cursor == null) { - // DO NOT RETURN NULL - // null checks are ugly! - return Collections.emptyList(); - } - - while (cursor.moveToNext()) { - T object = Reflect.on(clazz).create().get(); - - if (tableInfo.containID) { - DatabaseDataType dataType = SQLTypeParser.getDataType(tableInfo.primaryField); - String idFieldName = tableInfo.primaryField.getName(); - ValueHelper.setKeyValue(cursor, object, tableInfo.primaryField, dataType, cursor.getColumnIndex(idFieldName)); - } - - for (Field field : tableInfo.fieldToDataTypeMap.keySet()) { - DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field); - ValueHelper.setKeyValue(cursor, object, field, dataType, cursor.getColumnIndex(field.getName())); - } - list.add(object); - } - return list; - } - } - - /** - * 根据where语句寻找Bean - * - * @param clazz - * @param - * @return - */ - public List findBeanByWhere(Class clazz, String where) { - createTableIfNeed(clazz); - TableInfo tableInfo = TableHelper.from(clazz); - String statement = SQLStatementHelper.findByWhere(tableInfo, where); - if (neoTermSQLiteConfig.debugMode) { - NLog.INSTANCE.w(statement); - } - List list = new ArrayList<>(); - try (Cursor cursor = db.rawQuery(statement, null)) { - if (cursor == null) { - // DO NOT RETURN NULL - // null checks are ugly! - return Collections.emptyList(); - } - - while (cursor.moveToNext()) { - T object = Reflect.on(clazz).create().get(); - if (tableInfo.containID) { - DatabaseDataType dataType = SQLTypeParser.getDataType(tableInfo.primaryField); - String idFieldName = tableInfo.primaryField.getName(); - ValueHelper.setKeyValue(cursor, object, tableInfo.primaryField, dataType, cursor.getColumnIndex(idFieldName)); - } - for (Field field : tableInfo.fieldToDataTypeMap.keySet()) { - DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field); - ValueHelper.setKeyValue(cursor, object, field, dataType, cursor.getColumnIndex(field.getName())); - } - list.add(object); - } - return list; - } - } - - public T findOneBeanByWhere(Class clazz, String where) { - List list = findBeanByWhere(clazz, where); - if (!list.isEmpty()) { - return list.get(0); - } - return null; - } - - /** - * 根据where语句删除Bean - * - * @param clazz - * @return - */ - public NeoTermDatabase deleteBeanByWhere(Class clazz, String where) { - createTableIfNeed(clazz); - TableInfo tableInfo = TableHelper.from(clazz); - String statement = SQLStatementHelper.deleteByWhere(tableInfo, where); - if (neoTermSQLiteConfig.debugMode) { - NLog.INSTANCE.w(statement); - } - try { - db.execSQL(statement); - } catch (SQLException ignore) { - ignore.printStackTrace(); - } - - return this; - } - - /** - * 删除指定ID的bean - * - * @param tableClass - * @param id - * @return 删除的Bean - */ - public NeoTermDatabase deleteBeanByID(Class tableClass, Object id) { - createTableIfNeed(tableClass); - TableInfo tableInfo = TableHelper.from(tableClass); - DatabaseDataType dataType = SQLTypeParser.getDataType(id.getClass()); - if (dataType != null && tableInfo.primaryField != null) { - //判断ID类型是否与数据类型匹配 - boolean match = SQLTypeParser.matchType(tableInfo.primaryField, dataType); - if (!match) {//不匹配,抛出异常 - throw new IllegalArgumentException("类型 " + id.getClass().getName() + " 不是主键的类型,主键的类型应该为 " + tableInfo.primaryField.getType().getName()); - } - } - String idValue = ValueHelper.valueToString(dataType, id); - String statement = SQLStatementHelper.deleteByWhere(tableInfo, tableInfo.primaryField == null ? "_id" : tableInfo.primaryField.getName() + " = " + idValue); - if (neoTermSQLiteConfig.debugMode) { - NLog.INSTANCE.w(statement); - } - - try { - db.execSQL(statement); - } catch (SQLException ignore) { - ignore.printStackTrace(); - //删除失败 - } - return this; - - } - - /** - * 根据给定的where更新数据 - * - * @param tableClass - * @param where - * @param bean - * @return - */ - public NeoTermDatabase updateByWhere(Class tableClass, String where, Object bean) { - createTableIfNeed(tableClass); - TableInfo tableInfo = TableHelper.from(tableClass); - String statement = SQLStatementHelper.updateByWhere(tableInfo, bean, where); - if (neoTermSQLiteConfig.debugMode) { - NLog.INSTANCE.d(statement); - } - db.execSQL(statement); - return this; - } - - /** - * 根据给定的id更新数据 - * - * @param tableClass - * @param id - * @param bean - * @return - */ - public NeoTermDatabase updateByID(Class tableClass, Object id, Object bean) { - createTableIfNeed(tableClass); - TableInfo tableInfo = TableHelper.from(tableClass); - StringBuilder subStatement = new StringBuilder(); if (tableInfo.containID) { - subStatement.append(tableInfo.primaryField.getName()).append(" = ").append(ValueHelper.valueToString(SQLTypeParser.getDataType(tableInfo.primaryField), id)); - } else { - subStatement.append("_id = ").append((int) id); + DatabaseDataType dataType = SQLTypeParser.getDataType(tableInfo.primaryField); + String idFieldName = tableInfo.primaryField.getName(); + ValueHelper.setKeyValue(cursor, object, tableInfo.primaryField, dataType, cursor.getColumnIndex(idFieldName)); } - updateByWhere(tableClass, subStatement.toString(), bean); - return this; + for (Field field : tableInfo.fieldToDataTypeMap.keySet()) { + DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field); + ValueHelper.setKeyValue(cursor, object, field, dataType, cursor.getColumnIndex(field.getName())); + } + list.add(object); + } + return list; + } + } + + /** + * 根据where语句寻找Bean + * + * @param clazz + * @param + * @return + */ + public List findBeanByWhere(Class clazz, String where) { + createTableIfNeed(clazz); + TableInfo tableInfo = TableHelper.from(clazz); + String statement = SQLStatementHelper.findByWhere(tableInfo, where); + if (neoTermSQLiteConfig.debugMode) { + NLog.INSTANCE.w(statement); + } + List list = new ArrayList<>(); + try (Cursor cursor = db.rawQuery(statement, null)) { + if (cursor == null) { + // DO NOT RETURN NULL + // null checks are ugly! + return Collections.emptyList(); + } + + while (cursor.moveToNext()) { + T object = Reflect.on(clazz).create().get(); + if (tableInfo.containID) { + DatabaseDataType dataType = SQLTypeParser.getDataType(tableInfo.primaryField); + String idFieldName = tableInfo.primaryField.getName(); + ValueHelper.setKeyValue(cursor, object, tableInfo.primaryField, dataType, cursor.getColumnIndex(idFieldName)); + } + for (Field field : tableInfo.fieldToDataTypeMap.keySet()) { + DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field); + ValueHelper.setKeyValue(cursor, object, field, dataType, cursor.getColumnIndex(field.getName())); + } + list.add(object); + } + return list; + } + } + + public T findOneBeanByWhere(Class clazz, String where) { + List list = findBeanByWhere(clazz, where); + if (!list.isEmpty()) { + return list.get(0); + } + return null; + } + + /** + * 根据where语句删除Bean + * + * @param clazz + * @return + */ + public NeoTermDatabase deleteBeanByWhere(Class clazz, String where) { + createTableIfNeed(clazz); + TableInfo tableInfo = TableHelper.from(clazz); + String statement = SQLStatementHelper.deleteByWhere(tableInfo, where); + if (neoTermSQLiteConfig.debugMode) { + NLog.INSTANCE.w(statement); + } + try { + db.execSQL(statement); + } catch (SQLException ignore) { + ignore.printStackTrace(); } - /** - * 根据ID查找Bean - * - * @param tableClass - * @param id - * @param - * @return - */ - public T findBeanByID(Class tableClass, Object id) { - createTableIfNeed(tableClass); - TableInfo tableInfo = TableHelper.from(tableClass); - DatabaseDataType dataType = SQLTypeParser.getDataType(id.getClass()); - if (dataType == null) { - return null; - } - // 判断ID类型是否与数据类型匹配 - boolean match = SQLTypeParser.matchType(tableInfo.primaryField, dataType) || tableInfo.primaryField == null; - if (!match) {// 不匹配,抛出异常 - throw new IllegalArgumentException("Type " + id.getClass().getName() + " is not the primary key, expecting " + tableInfo.primaryField.getType().getName()); - } - String idValue = ValueHelper.valueToString(dataType, id); - String statement = SQLStatementHelper.findByWhere(tableInfo, tableInfo.primaryField == null ? "_id" : tableInfo.primaryField.getName() + " = " + idValue); - if (neoTermSQLiteConfig.debugMode) { - NLog.INSTANCE.w(statement); - } + return this; + } - try (Cursor cursor = db.rawQuery(statement, null)) { - if (cursor != null && cursor.getCount() > 0) { - cursor.moveToFirst(); - T bean = Reflect.on(tableClass).create().get(); - for (Field field : tableInfo.fieldToDataTypeMap.keySet()) { - DatabaseDataType fieldType = tableInfo.fieldToDataTypeMap.get(field); - ValueHelper.setKeyValue(cursor, bean, field, fieldType, cursor.getColumnIndex(field.getName())); - } - try { - Reflect.on(bean).set(tableInfo.containID ? tableInfo.primaryField.getName() : "_id", id); - } catch (Throwable ignore) { - // 我们允许Bean没有id字段,因此此异常可以忽略 - } - return bean; - } - return null; - } + /** + * 删除指定ID的bean + * + * @param tableClass + * @param id + * @return 删除的Bean + */ + public NeoTermDatabase deleteBeanByID(Class tableClass, Object id) { + createTableIfNeed(tableClass); + TableInfo tableInfo = TableHelper.from(tableClass); + DatabaseDataType dataType = SQLTypeParser.getDataType(id.getClass()); + if (dataType != null && tableInfo.primaryField != null) { + //判断ID类型是否与数据类型匹配 + boolean match = SQLTypeParser.matchType(tableInfo.primaryField, dataType); + if (!match) {//不匹配,抛出异常 + throw new IllegalArgumentException("类型 " + id.getClass().getName() + " 不是主键的类型,主键的类型应该为 " + tableInfo.primaryField.getType().getName()); + } + } + String idValue = ValueHelper.valueToString(dataType, id); + String statement = SQLStatementHelper.deleteByWhere(tableInfo, tableInfo.primaryField == null ? "_id" : tableInfo.primaryField.getName() + " = " + idValue); + if (neoTermSQLiteConfig.debugMode) { + NLog.INSTANCE.w(statement); } - /** - * 通过 VACUUM 命令压缩数据库 - */ - public void vacuum() { - db.execSQL("VACUUM"); + try { + db.execSQL(statement); + } catch (SQLException ignore) { + ignore.printStackTrace(); + //删除失败 + } + return this; + + } + + /** + * 根据给定的where更新数据 + * + * @param tableClass + * @param where + * @param bean + * @return + */ + public NeoTermDatabase updateByWhere(Class tableClass, String where, Object bean) { + createTableIfNeed(tableClass); + TableInfo tableInfo = TableHelper.from(tableClass); + String statement = SQLStatementHelper.updateByWhere(tableInfo, bean, where); + if (neoTermSQLiteConfig.debugMode) { + NLog.INSTANCE.d(statement); + } + db.execSQL(statement); + return this; + } + + /** + * 根据给定的id更新数据 + * + * @param tableClass + * @param id + * @param bean + * @return + */ + public NeoTermDatabase updateByID(Class tableClass, Object id, Object bean) { + createTableIfNeed(tableClass); + TableInfo tableInfo = TableHelper.from(tableClass); + StringBuilder subStatement = new StringBuilder(); + if (tableInfo.containID) { + subStatement.append(tableInfo.primaryField.getName()).append(" = ").append(ValueHelper.valueToString(SQLTypeParser.getDataType(tableInfo.primaryField), id)); + } else { + subStatement.append("_id = ").append((int) id); + } + updateByWhere(tableClass, subStatement.toString(), bean); + + return this; + } + + /** + * 根据ID查找Bean + * + * @param tableClass + * @param id + * @param + * @return + */ + public T findBeanByID(Class tableClass, Object id) { + createTableIfNeed(tableClass); + TableInfo tableInfo = TableHelper.from(tableClass); + DatabaseDataType dataType = SQLTypeParser.getDataType(id.getClass()); + if (dataType == null) { + return null; + } + // 判断ID类型是否与数据类型匹配 + boolean match = SQLTypeParser.matchType(tableInfo.primaryField, dataType) || tableInfo.primaryField == null; + if (!match) {// 不匹配,抛出异常 + throw new IllegalArgumentException("Type " + id.getClass().getName() + " is not the primary key, expecting " + tableInfo.primaryField.getType().getName()); + } + String idValue = ValueHelper.valueToString(dataType, id); + String statement = SQLStatementHelper.findByWhere(tableInfo, tableInfo.primaryField == null ? "_id" : tableInfo.primaryField.getName() + " = " + idValue); + if (neoTermSQLiteConfig.debugMode) { + NLog.INSTANCE.w(statement); } - /** - * 调用本方法会释放当前数据库占用的内存, - * 调用后请确保你不会在接下来的代码中继续用到本实例. - */ - public void destroy() { - DAO_MAP.remove(this); - this.neoTermSQLiteConfig = null; - this.db = null; - } - - /** - * 取得内部操纵的SqliteDatabase. - * - * @return - */ - public SQLiteDatabase getDatabase() { - return db; - } - - /** - * 内部数据库监听器,负责派发接口. - */ - private class SQLiteDataBaseHelper extends SQLiteOpenHelper { - private final OnDatabaseUpgradedListener onDatabaseUpgradedListener; - private final boolean defaultDropAllTables; - - public SQLiteDataBaseHelper(Context context, NeoTermSQLiteConfig config) { - super(context, config.getDatabaseName(), null, config.getDatabaseVersion()); - this.onDatabaseUpgradedListener = config.getOnDatabaseUpgradedListener(); - this.defaultDropAllTables = config.isDefaultDropAllTables(); + try (Cursor cursor = db.rawQuery(statement, null)) { + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + T bean = Reflect.on(tableClass).create().get(); + for (Field field : tableInfo.fieldToDataTypeMap.keySet()) { + DatabaseDataType fieldType = tableInfo.fieldToDataTypeMap.get(field); + ValueHelper.setKeyValue(cursor, bean, field, fieldType, cursor.getColumnIndex(field.getName())); } - - @Override - public void onCreate(SQLiteDatabase db) { - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (onDatabaseUpgradedListener != null) { - onDatabaseUpgradedListener.onDatabaseUpgraded(db, oldVersion, newVersion); - - } else if (defaultDropAllTables) { // 干掉所有的表 - dropAllTable(); - } + try { + Reflect.on(bean).set(tableInfo.containID ? tableInfo.primaryField.getName() : "_id", id); + } catch (Throwable ignore) { + // 我们允许Bean没有id字段,因此此异常可以忽略 } + return bean; + } + return null; } + } + + /** + * 通过 VACUUM 命令压缩数据库 + */ + public void vacuum() { + db.execSQL("VACUUM"); + } + + /** + * 调用本方法会释放当前数据库占用的内存, + * 调用后请确保你不会在接下来的代码中继续用到本实例. + */ + public void destroy() { + DAO_MAP.remove(this); + this.neoTermSQLiteConfig = null; + this.db = null; + } + + /** + * 取得内部操纵的SqliteDatabase. + * + * @return + */ + public SQLiteDatabase getDatabase() { + return db; + } + + /** + * 内部数据库监听器,负责派发接口. + */ + private class SQLiteDataBaseHelper extends SQLiteOpenHelper { + private final OnDatabaseUpgradedListener onDatabaseUpgradedListener; + private final boolean defaultDropAllTables; + + public SQLiteDataBaseHelper(Context context, NeoTermSQLiteConfig config) { + super(context, config.getDatabaseName(), null, config.getDatabaseVersion()); + this.onDatabaseUpgradedListener = config.getOnDatabaseUpgradedListener(); + this.defaultDropAllTables = config.isDefaultDropAllTables(); + } + + @Override + public void onCreate(SQLiteDatabase db) { + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (onDatabaseUpgradedListener != null) { + onDatabaseUpgradedListener.onDatabaseUpgraded(db, oldVersion, newVersion); + + } else if (defaultDropAllTables) { // 干掉所有的表 + dropAllTable(); + } + } + } } diff --git a/app/src/main/java/io/neoterm/framework/database/DatabaseDataType.java b/app/src/main/java/io/neoterm/framework/database/DatabaseDataType.java index 2915468..c3b84cb 100644 --- a/app/src/main/java/io/neoterm/framework/database/DatabaseDataType.java +++ b/app/src/main/java/io/neoterm/framework/database/DatabaseDataType.java @@ -4,35 +4,35 @@ package io.neoterm.framework.database; * @author kiva */ public enum DatabaseDataType { - /** - * int类型 - */ - INTEGER, - /** - * String类型 - */ - TEXT, - /** - * float类型 - */ - FLOAT, - /** - * long类型 - */ - BIGINT, - /** - * double类型 - */ - DOUBLE; + /** + * int类型 + */ + INTEGER, + /** + * String类型 + */ + TEXT, + /** + * float类型 + */ + FLOAT, + /** + * long类型 + */ + BIGINT, + /** + * double类型 + */ + DOUBLE; - boolean nullable = true; + boolean nullable = true; - /** - * 数据类型是否允许为null - */ - public DatabaseDataType nullable(boolean nullable) { - this.nullable = nullable; - return this; - } + /** + * 数据类型是否允许为null + */ + public DatabaseDataType nullable(boolean nullable) { + this.nullable = nullable; + return this; + } } diff --git a/app/src/main/java/io/neoterm/framework/database/NeoTermSQLiteConfig.java b/app/src/main/java/io/neoterm/framework/database/NeoTermSQLiteConfig.java index 3b832ac..c56ded1 100644 --- a/app/src/main/java/io/neoterm/framework/database/NeoTermSQLiteConfig.java +++ b/app/src/main/java/io/neoterm/framework/database/NeoTermSQLiteConfig.java @@ -7,117 +7,119 @@ import java.io.Serializable; */ public class NeoTermSQLiteConfig implements Serializable { - private static final long serialVersionUID = -4069725570156436316L; - //============================================================== - // 常量 - //============================================================== - public static String DEFAULT_DB_NAME = "we_like.db"; - public static NeoTermSQLiteConfig DEFAULT_CONFIG = new NeoTermSQLiteConfig(); + private static final long serialVersionUID = -4069725570156436316L; + //============================================================== + // 常量 + //============================================================== + public static String DEFAULT_DB_NAME = "we_like.db"; + public static NeoTermSQLiteConfig DEFAULT_CONFIG = new NeoTermSQLiteConfig(); - //============================================================== - // 字段 - //============================================================== - /** - * 是否为DEBUG模式 - */ - public boolean debugMode = false; - /** - * 数据库名 - */ - private String dbName = DEFAULT_DB_NAME; - /** - * 数据库升级监听器 - */ - private OnDatabaseUpgradedListener onDatabaseUpgradedListener; - private boolean defaultDropAllTables = false; - private String saveDir; - private int dbVersion = 1; + //============================================================== + // 字段 + //============================================================== + /** + * 是否为DEBUG模式 + */ + public boolean debugMode = false; + /** + * 数据库名 + */ + private String dbName = DEFAULT_DB_NAME; + /** + * 数据库升级监听器 + */ + private OnDatabaseUpgradedListener onDatabaseUpgradedListener; + private boolean defaultDropAllTables = false; + private String saveDir; + private int dbVersion = 1; - /** - * 取得数据库的名称 - * - * @return - */ - public String getDatabaseName() { - return dbName; - } + /** + * 取得数据库的名称 + * + * @return + */ + public String getDatabaseName() { + return dbName; + } - /** - * 设置数据库的名称 - * - * @param dbName - */ - public void setDatabaseName(String dbName) { - this.dbName = dbName; - } + /** + * 设置数据库的名称 + * + * @param dbName + */ + public void setDatabaseName(String dbName) { + this.dbName = dbName; + } - /** - * 取得数据库升级监听器 - * - * @return - */ - public OnDatabaseUpgradedListener getOnDatabaseUpgradedListener() { - return onDatabaseUpgradedListener; - } + /** + * 取得数据库升级监听器 + * + * @return + */ + public OnDatabaseUpgradedListener getOnDatabaseUpgradedListener() { + return onDatabaseUpgradedListener; + } - /** - * 设置数据库升级监听器 - * - * @param onDatabaseUpgradedListener - */ - public void setOnDatabaseUpgradedListener(OnDatabaseUpgradedListener onDatabaseUpgradedListener) { - this.onDatabaseUpgradedListener = onDatabaseUpgradedListener; - } + /** + * 设置数据库升级监听器 + * + * @param onDatabaseUpgradedListener + */ + public void setOnDatabaseUpgradedListener(OnDatabaseUpgradedListener onDatabaseUpgradedListener) { + this.onDatabaseUpgradedListener = onDatabaseUpgradedListener; + } - /** - * 取得数据库保存目录 - * - * @return - */ - public String getSaveDir() { - return saveDir; - } + /** + * 取得数据库保存目录 + * + * @return + */ + public String getSaveDir() { + return saveDir; + } - /** - * 设置数据库的保存目录 - * - * @param saveDir - */ - public void setSaveDir(String saveDir) { - this.saveDir = saveDir; - } + /** + * 设置数据库的保存目录 + * + * @param saveDir + */ + public void setSaveDir(String saveDir) { + this.saveDir = saveDir; + } - /** - * 获取DB的版本号 - * - * @return - */ - public int getDatabaseVersion() { - return dbVersion; - } + /** + * 获取DB的版本号 + * + * @return + */ + public int getDatabaseVersion() { + return dbVersion; + } - /** - * 设置DB的版本号 - * - * @param dbVersion - */ - public void setDatabaseVersion(int dbVersion) { - this.dbVersion = dbVersion; - } + /** + * 设置DB的版本号 + * + * @param dbVersion + */ + public void setDatabaseVersion(int dbVersion) { + this.dbVersion = dbVersion; + } - /** - * App 更新时是否默认删除所有存在的表 - * @return - */ - public boolean isDefaultDropAllTables() { - return defaultDropAllTables; - } + /** + * App 更新时是否默认删除所有存在的表 + * + * @return + */ + public boolean isDefaultDropAllTables() { + return defaultDropAllTables; + } - /** - * 设置 App 更新时是否默认删除所有存在的表 - * @param defaultDropAllTables - */ - public void setDefaultDropAllTables(boolean defaultDropAllTables) { - this.defaultDropAllTables = defaultDropAllTables; - } + /** + * 设置 App 更新时是否默认删除所有存在的表 + * + * @param defaultDropAllTables + */ + public void setDefaultDropAllTables(boolean defaultDropAllTables) { + this.defaultDropAllTables = defaultDropAllTables; + } } diff --git a/app/src/main/java/io/neoterm/framework/database/OnDatabaseUpgradedListener.java b/app/src/main/java/io/neoterm/framework/database/OnDatabaseUpgradedListener.java index bf600a3..5c3a020 100644 --- a/app/src/main/java/io/neoterm/framework/database/OnDatabaseUpgradedListener.java +++ b/app/src/main/java/io/neoterm/framework/database/OnDatabaseUpgradedListener.java @@ -6,10 +6,10 @@ import android.database.sqlite.SQLiteDatabase; * @author kiva */ public interface OnDatabaseUpgradedListener { - /** - * @param db 数据库 - * @param oldVersion 旧版本 - * @param newVersion 新版本 - */ - void onDatabaseUpgraded(SQLiteDatabase db, int oldVersion, int newVersion); + /** + * @param db 数据库 + * @param oldVersion 旧版本 + * @param newVersion 新版本 + */ + void onDatabaseUpgraded(SQLiteDatabase db, int oldVersion, int newVersion); } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/framework/database/SQLStatementHelper.java b/app/src/main/java/io/neoterm/framework/database/SQLStatementHelper.java index 5d5edd8..9a74fcb 100644 --- a/app/src/main/java/io/neoterm/framework/database/SQLStatementHelper.java +++ b/app/src/main/java/io/neoterm/framework/database/SQLStatementHelper.java @@ -1,198 +1,198 @@ package io.neoterm.framework.database; -import java.lang.reflect.Field; - import io.neoterm.framework.database.annotation.ID; import io.neoterm.framework.database.bean.TableInfo; +import java.lang.reflect.Field; + /** * @author kiva */ public class SQLStatementHelper { - /** - * 构造创建表的语句 - * - * @param tableInfo 表信息 - * @return 创建表的SQL语句 - */ - public static String createTable(TableInfo tableInfo) { - StringBuilder statement = new StringBuilder(); + /** + * 构造创建表的语句 + * + * @param tableInfo 表信息 + * @return 创建表的SQL语句 + */ + public static String createTable(TableInfo tableInfo) { + StringBuilder statement = new StringBuilder(); - statement.append("CREATE TABLE ").append("'") - .append(tableInfo.tableName).append("'") - .append(" ("); + statement.append("CREATE TABLE ").append("'") + .append(tableInfo.tableName).append("'") + .append(" ("); - if (tableInfo.containID) { - DatabaseDataType dataType = SQLTypeParser.getDataType(tableInfo.primaryField); - if (dataType == null) { - throw new IllegalArgumentException("Type of " + tableInfo.primaryField.getType().getName() + " is not support in WelikeDB."); - } - statement.append("'").append(tableInfo.primaryField.getName()).append("'"); - switch (dataType) { - case INTEGER: - statement.append(" INTEGER PRIMARY KEY "); - ID id = tableInfo.primaryField.getAnnotation(ID.class); - if (id != null && id.autoIncrement()) { - statement.append("AUTOINCREMENT"); - } - break; - default: - statement - .append(" ") - .append(dataType.name()) - .append(" PRIMARY KEY"); - } + if (tableInfo.containID) { + DatabaseDataType dataType = SQLTypeParser.getDataType(tableInfo.primaryField); + if (dataType == null) { + throw new IllegalArgumentException("Type of " + tableInfo.primaryField.getType().getName() + " is not support in WelikeDB."); + } + statement.append("'").append(tableInfo.primaryField.getName()).append("'"); + switch (dataType) { + case INTEGER: + statement.append(" INTEGER PRIMARY KEY "); + ID id = tableInfo.primaryField.getAnnotation(ID.class); + if (id != null && id.autoIncrement()) { + statement.append("AUTOINCREMENT"); + } + break; + default: + statement + .append(" ") + .append(dataType.name()) + .append(" PRIMARY KEY"); + } - statement.append(","); + statement.append(","); - } else { - statement.append("'_id' INTEGER PRIMARY KEY AUTOINCREMENT,"); - } - - - for (Field field : tableInfo.fieldToDataTypeMap.keySet()) { - DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field); - statement.append("'").append(field.getName()).append("'") - .append(" ") - .append(dataType.name()); - if (!dataType.nullable) { - statement.append(" NOT NULL"); - } - statement.append(","); - } - //删掉最后一个逗号 - statement.deleteCharAt(statement.length() - 1); - statement.append(")"); - - return statement.toString(); - } - - /** - * 构建 插入一个Bean 的语句. - * - * @param o - * @return - */ - public static String insertIntoTable(Object o) { - TableInfo tableInfo = TableHelper.from(o.getClass()); - StringBuilder statement = new StringBuilder(); - statement.append("INSERT INTO ").append(tableInfo.tableName).append(" "); - statement.append("VALUES("); - - if (tableInfo.containID) { - DatabaseDataType primaryDataType = SQLTypeParser.getDataType(tableInfo.primaryField); - switch (primaryDataType) { - case INTEGER: - statement.append("NULL,"); - break; - default: - try { - statement - .append(ValueHelper.valueToString(primaryDataType, tableInfo.primaryField, o)) - .append(","); - } catch (IllegalAccessException ignored) { - } - break; - } - - } else { - statement.append("NULL,"); - } - - for (Field field : tableInfo.fieldToDataTypeMap.keySet()) { - DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field); - try { - statement.append(ValueHelper.valueToString(dataType, field, o)).append(","); - } catch (IllegalAccessException e) { - //不会发生... - } - } - statement.deleteCharAt(statement.length() - 1); - statement.append(")"); - - return statement.toString(); - - } - - /** - * 根据where条件创建选择语句 - * - * @param tableInfo - * @param where - * @return - */ - public static String findByWhere(TableInfo tableInfo, String where) { - StringBuilder statement = new StringBuilder("SELECT * FROM "); - statement - .append(tableInfo.tableName) - .append(" ") - .append("WHERE ") - .append(where); - - return statement.toString(); + } else { + statement.append("'_id' INTEGER PRIMARY KEY AUTOINCREMENT,"); } - /** - * 根据where条件创建删除语句 - * - * @param tableInfo - * @param where - * @return - */ - public static String deleteByWhere(TableInfo tableInfo, String where) { - StringBuilder statement = new StringBuilder("DELETE FROM "); - statement - .append(tableInfo.tableName) - .append(" ") - .append("WHERE ") - .append(where); + for (Field field : tableInfo.fieldToDataTypeMap.keySet()) { + DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field); + statement.append("'").append(field.getName()).append("'") + .append(" ") + .append(dataType.name()); + if (!dataType.nullable) { + statement.append(" NOT NULL"); + } + statement.append(","); + } + //删掉最后一个逗号 + statement.deleteCharAt(statement.length() - 1); + statement.append(")"); - return statement.toString(); + return statement.toString(); + } + + /** + * 构建 插入一个Bean 的语句. + * + * @param o + * @return + */ + public static String insertIntoTable(Object o) { + TableInfo tableInfo = TableHelper.from(o.getClass()); + StringBuilder statement = new StringBuilder(); + statement.append("INSERT INTO ").append(tableInfo.tableName).append(" "); + statement.append("VALUES("); + + if (tableInfo.containID) { + DatabaseDataType primaryDataType = SQLTypeParser.getDataType(tableInfo.primaryField); + switch (primaryDataType) { + case INTEGER: + statement.append("NULL,"); + break; + default: + try { + statement + .append(ValueHelper.valueToString(primaryDataType, tableInfo.primaryField, o)) + .append(","); + } catch (IllegalAccessException ignored) { + } + break; + } + + } else { + statement.append("NULL,"); } - /** - * 根据where条件创建更新语句 - * - * @param tableInfo - * @param bean - * @param where - * @return - */ - public static String updateByWhere(TableInfo tableInfo, Object bean, String where) { - StringBuilder builder = new StringBuilder("UPDATE "); + for (Field field : tableInfo.fieldToDataTypeMap.keySet()) { + DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field); + try { + statement.append(ValueHelper.valueToString(dataType, field, o)).append(","); + } catch (IllegalAccessException e) { + //不会发生... + } + } + statement.deleteCharAt(statement.length() - 1); + statement.append(")"); - builder.append(tableInfo.tableName).append(" SET "); + return statement.toString(); - for (Field f : tableInfo.fieldToDataTypeMap.keySet()) { + } - try { - builder.append(f.getName()) - .append(" = ") - .append(ValueHelper.valueToString( - SQLTypeParser.getDataType(f.getType()), - f.get(bean))).append(","); - } catch (Throwable ignored) { - } - } + /** + * 根据where条件创建选择语句 + * + * @param tableInfo + * @param where + * @return + */ + public static String findByWhere(TableInfo tableInfo, String where) { + StringBuilder statement = new StringBuilder("SELECT * FROM "); + statement + .append(tableInfo.tableName) + .append(" ") + .append("WHERE ") + .append(where); - builder.deleteCharAt(builder.length() - 1);//删除最后一个逗号 + return statement.toString(); + } - builder.append(" WHERE "); - builder.append(where); - return builder.toString(); + + /** + * 根据where条件创建删除语句 + * + * @param tableInfo + * @param where + * @return + */ + public static String deleteByWhere(TableInfo tableInfo, String where) { + StringBuilder statement = new StringBuilder("DELETE FROM "); + statement + .append(tableInfo.tableName) + .append(" ") + .append("WHERE ") + .append(where); + + return statement.toString(); + } + + /** + * 根据where条件创建更新语句 + * + * @param tableInfo + * @param bean + * @param where + * @return + */ + public static String updateByWhere(TableInfo tableInfo, Object bean, String where) { + StringBuilder builder = new StringBuilder("UPDATE "); + + builder.append(tableInfo.tableName).append(" SET "); + + for (Field f : tableInfo.fieldToDataTypeMap.keySet()) { + + try { + builder.append(f.getName()) + .append(" = ") + .append(ValueHelper.valueToString( + SQLTypeParser.getDataType(f.getType()), + f.get(bean))).append(","); + } catch (Throwable ignored) { + } } - /** - * 创建选中table的语句 - * - * @param tableName - * @return - */ - public static String selectTable(String tableName) { - return "SELECT * FROM " + tableName; - } + builder.deleteCharAt(builder.length() - 1);//删除最后一个逗号 + + builder.append(" WHERE "); + builder.append(where); + return builder.toString(); + } + + /** + * 创建选中table的语句 + * + * @param tableName + * @return + */ + public static String selectTable(String tableName) { + return "SELECT * FROM " + tableName; + } } diff --git a/app/src/main/java/io/neoterm/framework/database/SQLTypeParser.java b/app/src/main/java/io/neoterm/framework/database/SQLTypeParser.java index 10410d7..6f37d75 100644 --- a/app/src/main/java/io/neoterm/framework/database/SQLTypeParser.java +++ b/app/src/main/java/io/neoterm/framework/database/SQLTypeParser.java @@ -1,81 +1,81 @@ package io.neoterm.framework.database; -import java.lang.reflect.Field; - import io.neoterm.framework.database.annotation.Ignore; import io.neoterm.framework.database.annotation.NotNull; +import java.lang.reflect.Field; + /** * @author kiva */ public class SQLTypeParser { - /** - * 根据字段类型匹配它在数据库中的对应类型. - * - * @param field - * @return - */ - public static DatabaseDataType getDataType(Field field) { - Class clazz = field.getType(); - if (clazz == (String.class)) { - return DatabaseDataType.TEXT.nullable((field.getAnnotation(NotNull.class) == null)); - } else if (clazz == (int.class) || clazz == (Integer.class)) { - return DatabaseDataType.INTEGER.nullable((field.getAnnotation(NotNull.class) == null)); - } else if (clazz == (float.class) || clazz == (Float.class)) { - return DatabaseDataType.FLOAT.nullable((field.getAnnotation(NotNull.class) == null)); - } else if (clazz == (long.class) || clazz == (Long.class)) { - return DatabaseDataType.BIGINT.nullable((field.getAnnotation(NotNull.class) == null)); - } else if (clazz == (double.class) || clazz == (Double.class)) { - return DatabaseDataType.DOUBLE.nullable((field.getAnnotation(NotNull.class) == null)); - } else if (clazz == (boolean.class) || clazz == (Boolean.class)) { - return DatabaseDataType.INTEGER.nullable((field.getAnnotation(NotNull.class) == null)); - } - return null; + /** + * 根据字段类型匹配它在数据库中的对应类型. + * + * @param field + * @return + */ + public static DatabaseDataType getDataType(Field field) { + Class clazz = field.getType(); + if (clazz == (String.class)) { + return DatabaseDataType.TEXT.nullable((field.getAnnotation(NotNull.class) == null)); + } else if (clazz == (int.class) || clazz == (Integer.class)) { + return DatabaseDataType.INTEGER.nullable((field.getAnnotation(NotNull.class) == null)); + } else if (clazz == (float.class) || clazz == (Float.class)) { + return DatabaseDataType.FLOAT.nullable((field.getAnnotation(NotNull.class) == null)); + } else if (clazz == (long.class) || clazz == (Long.class)) { + return DatabaseDataType.BIGINT.nullable((field.getAnnotation(NotNull.class) == null)); + } else if (clazz == (double.class) || clazz == (Double.class)) { + return DatabaseDataType.DOUBLE.nullable((field.getAnnotation(NotNull.class) == null)); + } else if (clazz == (boolean.class) || clazz == (Boolean.class)) { + return DatabaseDataType.INTEGER.nullable((field.getAnnotation(NotNull.class) == null)); } + return null; + } - /** - * 根据字段类型匹配它在数据库中的对应类型. - * - * @param clazz - * @return - */ - public static DatabaseDataType getDataType(Class clazz) { - if (clazz == (String.class)) { - return DatabaseDataType.TEXT; - } else if (clazz == (int.class) || clazz == (Integer.class)) { - return DatabaseDataType.INTEGER; - } else if (clazz == (float.class) || clazz == (Float.class)) { - return DatabaseDataType.FLOAT; - } else if (clazz == (long.class) || clazz == (Long.class)) { - return DatabaseDataType.BIGINT; - } else if (clazz == (double.class) || clazz == (Double.class)) { - return DatabaseDataType.DOUBLE; - } else if (clazz == (boolean.class) || clazz == (Boolean.class)) { - return DatabaseDataType.INTEGER; - } - return null; + /** + * 根据字段类型匹配它在数据库中的对应类型. + * + * @param clazz + * @return + */ + public static DatabaseDataType getDataType(Class clazz) { + if (clazz == (String.class)) { + return DatabaseDataType.TEXT; + } else if (clazz == (int.class) || clazz == (Integer.class)) { + return DatabaseDataType.INTEGER; + } else if (clazz == (float.class) || clazz == (Float.class)) { + return DatabaseDataType.FLOAT; + } else if (clazz == (long.class) || clazz == (Long.class)) { + return DatabaseDataType.BIGINT; + } else if (clazz == (double.class) || clazz == (Double.class)) { + return DatabaseDataType.DOUBLE; + } else if (clazz == (boolean.class) || clazz == (Boolean.class)) { + return DatabaseDataType.INTEGER; } + return null; + } - /** - * 字段类型与数据类型是否匹配? - * - * @param field - * @param dataType - * @return - */ - public static boolean matchType(Field field, DatabaseDataType dataType) { - DatabaseDataType fieldDataType = getDataType(field.getType()); + /** + * 字段类型与数据类型是否匹配? + * + * @param field + * @param dataType + * @return + */ + public static boolean matchType(Field field, DatabaseDataType dataType) { + DatabaseDataType fieldDataType = getDataType(field.getType()); - return dataType != null && fieldDataType == (dataType); - } + return dataType != null && fieldDataType == (dataType); + } - /** - * 字段是否可以被数据库忽略? - * - * @param field - * @return - */ - public static boolean isIgnore(Field field) { - return field.getAnnotation(Ignore.class) != null; - } + /** + * 字段是否可以被数据库忽略? + * + * @param field + * @return + */ + public static boolean isIgnore(Field field) { + return field.getAnnotation(Ignore.class) != null; + } } diff --git a/app/src/main/java/io/neoterm/framework/database/TableHelper.java b/app/src/main/java/io/neoterm/framework/database/TableHelper.java index d352a18..9ff03a9 100644 --- a/app/src/main/java/io/neoterm/framework/database/TableHelper.java +++ b/app/src/main/java/io/neoterm/framework/database/TableHelper.java @@ -1,131 +1,131 @@ package io.neoterm.framework.database; +import io.neoterm.framework.NeoTermDatabase; +import io.neoterm.framework.database.annotation.ID; +import io.neoterm.framework.database.annotation.Table; +import io.neoterm.framework.database.bean.TableInfo; + import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; -import io.neoterm.framework.NeoTermDatabase; -import io.neoterm.framework.database.annotation.ID; -import io.neoterm.framework.database.annotation.Table; -import io.neoterm.framework.database.bean.TableInfo; - /** * @author kiva */ public class TableHelper { - private static final Map, TableInfo> classToTableInfoMap = new HashMap<>(); + private static final Map, TableInfo> classToTableInfoMap = new HashMap<>(); - /** - * 根据传入的Bean的Class将其映射为一个TableInfo. - * - * @param clazz - * @return - */ - public static TableInfo from(Class clazz) { - TableInfo tableInfo = classToTableInfoMap.get(clazz); - if (tableInfo != null) { - return tableInfo; - } - tableInfo = new TableInfo(); - //Table注解解析 - Table table = clazz.getAnnotation(Table.class); - String afterTableCreateMethod = table != null ? table.afterTableCreate() : null; - if (afterTableCreateMethod != null && afterTableCreateMethod.trim().length() > 0) { - try { - Method method = clazz.getDeclaredMethod(afterTableCreateMethod, NeoTermDatabase.class); - if (method != null && Modifier.isStatic(method.getModifiers())) { - method.setAccessible(true); - tableInfo.afterTableCreateMethod = method; - } - } catch (Throwable ignored) { - } - } - if (table != null && table.name().trim().length() != 0) { - tableInfo.tableName = table.name(); - } else { - tableInfo.tableName = clazz.getName().replace(".", "_"); + /** + * 根据传入的Bean的Class将其映射为一个TableInfo. + * + * @param clazz + * @return + */ + public static TableInfo from(Class clazz) { + TableInfo tableInfo = classToTableInfoMap.get(clazz); + if (tableInfo != null) { + return tableInfo; + } + tableInfo = new TableInfo(); + //Table注解解析 + Table table = clazz.getAnnotation(Table.class); + String afterTableCreateMethod = table != null ? table.afterTableCreate() : null; + if (afterTableCreateMethod != null && afterTableCreateMethod.trim().length() > 0) { + try { + Method method = clazz.getDeclaredMethod(afterTableCreateMethod, NeoTermDatabase.class); + if (method != null && Modifier.isStatic(method.getModifiers())) { + method.setAccessible(true); + tableInfo.afterTableCreateMethod = method; } + } catch (Throwable ignored) { + } + } + if (table != null && table.name().trim().length() != 0) { + tableInfo.tableName = table.name(); + } else { + tableInfo.tableName = clazz.getName().replace(".", "_"); + } - Map fieldEnumMap = new HashMap<>(); - for (Field field : clazz.getDeclaredFields()) { - field.setAccessible(true); - //如果这个字段加了ignore注解,我们就跳过 - if (SQLTypeParser.isIgnore(field)) { - continue; - } - DatabaseDataType dataType = SQLTypeParser.getDataType(field); - if (dataType != null) { - fieldEnumMap.put(field, dataType); - } else { - throw new IllegalArgumentException("The type of " + field.getName() + " is not supported in database."); - } - } + Map fieldEnumMap = new HashMap<>(); + for (Field field : clazz.getDeclaredFields()) { + field.setAccessible(true); + //如果这个字段加了ignore注解,我们就跳过 + if (SQLTypeParser.isIgnore(field)) { + continue; + } + DatabaseDataType dataType = SQLTypeParser.getDataType(field); + if (dataType != null) { + fieldEnumMap.put(field, dataType); + } else { + throw new IllegalArgumentException("The type of " + field.getName() + " is not supported in database."); + } + } - tableInfo.fieldToDataTypeMap = fieldEnumMap; - buildPrimaryIDForTableInfo(tableInfo); - tableInfo.createTableStatement = SQLStatementHelper.createTable(tableInfo); + tableInfo.fieldToDataTypeMap = fieldEnumMap; + buildPrimaryIDForTableInfo(tableInfo); + tableInfo.createTableStatement = SQLStatementHelper.createTable(tableInfo); - synchronized (classToTableInfoMap) { - classToTableInfoMap.put(clazz, tableInfo); - } + synchronized (classToTableInfoMap) { + classToTableInfoMap.put(clazz, tableInfo); + } + return tableInfo; + } + + /** + * 为一个Bean匹配一个ID字段,如果ID字段不存在,使用默认的_id替代. + * + * @param info + * @return + */ + private static TableInfo buildPrimaryIDForTableInfo(TableInfo info) { + + Field idField = null; + ID id; + for (Field field : info.fieldToDataTypeMap.keySet()) { + id = field.getAnnotation(ID.class); + if (id != null) { + idField = field; + break; + } + }//end + if (idField != null) { + //从字段表中移除ID + info.fieldToDataTypeMap.remove(idField); + info.containID = true; + info.primaryField = idField; + } else { + info.containID = false; + info.primaryField = null; + } + + return info; + } + + /** + * 根据表名匹配TableInfo + * + * @param tableName + * @return + */ + public static TableInfo findTableInfoByName(String tableName) { + + for (TableInfo tableInfo : classToTableInfoMap.values()) { + if (tableInfo.tableName.equals(tableName)) { return tableInfo; + } } - /** - * 为一个Bean匹配一个ID字段,如果ID字段不存在,使用默认的_id替代. - * - * @param info - * @return - */ - private static TableInfo buildPrimaryIDForTableInfo(TableInfo info) { + return null; + } - Field idField = null; - ID id; - for (Field field : info.fieldToDataTypeMap.keySet()) { - id = field.getAnnotation(ID.class); - if (id != null) { - idField = field; - break; - } - }//end - if (idField != null) { - //从字段表中移除ID - info.fieldToDataTypeMap.remove(idField); - info.containID = true; - info.primaryField = idField; - } else { - info.containID = false; - info.primaryField = null; - } - - return info; - } - - /** - * 根据表名匹配TableInfo - * - * @param tableName - * @return - */ - public static TableInfo findTableInfoByName(String tableName) { - - for (TableInfo tableInfo : classToTableInfoMap.values()) { - if (tableInfo.tableName.equals(tableName)) { - return tableInfo; - } - } - - return null; - } - - /** - * 清除留在内存中的TableInfo缓存 - */ - public static void clearCache() { - classToTableInfoMap.clear(); - } + /** + * 清除留在内存中的TableInfo缓存 + */ + public static void clearCache() { + classToTableInfoMap.clear(); + } } diff --git a/app/src/main/java/io/neoterm/framework/database/ValueHelper.java b/app/src/main/java/io/neoterm/framework/database/ValueHelper.java index 14b34b2..ac4d37d 100644 --- a/app/src/main/java/io/neoterm/framework/database/ValueHelper.java +++ b/app/src/main/java/io/neoterm/framework/database/ValueHelper.java @@ -9,110 +9,110 @@ import java.lang.reflect.Field; */ public class ValueHelper { - /** - * 根据数据类型将数据库中的值写入到相应的字段. - * - * @param cursor 游标 - * @param object 赋值对象 - * @param field 赋值字段 - * @param dataType 数据类型 - */ - public static void setKeyValue(Cursor cursor, Object object, Field field, DatabaseDataType dataType, int index) { - switch (dataType) { - case INTEGER: - try { - field.set(object, cursor.getInt(index)); - } catch (Throwable e) { - try { - //支持Boolean类型 - //因为Boolean默认当Integer处理 - field.set(object, cursor.getInt(index) != 0); - } catch (IllegalAccessException ignored) { - } - } - break; - case TEXT: - try { - field.set(object, cursor.getString(index)); - } catch (IllegalAccessException e) { - } - break; - case FLOAT: - try { - field.set(object, cursor.getFloat(index)); - } catch (IllegalAccessException e) { - } - break; - case BIGINT: - try { - field.set(object, cursor.getLong(index)); - } catch (IllegalAccessException e) { - } - break; - case DOUBLE: - try { - field.set(object, cursor.getDouble(index)); - } catch (IllegalAccessException e) { - } - break; - + /** + * 根据数据类型将数据库中的值写入到相应的字段. + * + * @param cursor 游标 + * @param object 赋值对象 + * @param field 赋值字段 + * @param dataType 数据类型 + */ + public static void setKeyValue(Cursor cursor, Object object, Field field, DatabaseDataType dataType, int index) { + switch (dataType) { + case INTEGER: + try { + field.set(object, cursor.getInt(index)); + } catch (Throwable e) { + try { + //支持Boolean类型 + //因为Boolean默认当Integer处理 + field.set(object, cursor.getInt(index) != 0); + } catch (IllegalAccessException ignored) { + } } - } - - /** - * 根据数据类型从字段中提取值并转换为String - * - * @param dataType - * @param field - * @param o - * @return - * @throws IllegalAccessException 无法转换时抛出的异常 - */ - public static String valueToString(DatabaseDataType dataType, Field field, Object o) throws IllegalAccessException { - switch (dataType) { - case INTEGER: - Object f = field.get(o); - if (f instanceof Boolean) { - return String.valueOf(((boolean) field.get(o)) ? 1 : 0); - } else { - return String.valueOf((int) field.get(o)); - } - case TEXT: - return "\"" + field.get(o) + "" + "\""; - case DOUBLE: - return String.valueOf((double) field.get(o)); - case FLOAT: - return String.valueOf((float) field.get(o)); - case BIGINT: - return String.valueOf((long) field.get(o)); + break; + case TEXT: + try { + field.set(object, cursor.getString(index)); + } catch (IllegalAccessException e) { } - return null; - } - - /** - * 根据数据类型将对象转换为String - * - * @param dataType - * @param o - * @return - */ - public static String valueToString(DatabaseDataType dataType, Object o) { - switch (dataType) { - case INTEGER: - if (o instanceof Boolean) { - return ((boolean) o) ? "1" : "0"; - } else { - return String.valueOf((int) o); - } - case TEXT: - return "\"" + o + "\""; - case DOUBLE: - return String.valueOf((double) o); - case FLOAT: - return String.valueOf((float) o); - case BIGINT: - return String.valueOf((long) o); + break; + case FLOAT: + try { + field.set(object, cursor.getFloat(index)); + } catch (IllegalAccessException e) { } - return null; + break; + case BIGINT: + try { + field.set(object, cursor.getLong(index)); + } catch (IllegalAccessException e) { + } + break; + case DOUBLE: + try { + field.set(object, cursor.getDouble(index)); + } catch (IllegalAccessException e) { + } + break; + } + } + + /** + * 根据数据类型从字段中提取值并转换为String + * + * @param dataType + * @param field + * @param o + * @return + * @throws IllegalAccessException 无法转换时抛出的异常 + */ + public static String valueToString(DatabaseDataType dataType, Field field, Object o) throws IllegalAccessException { + switch (dataType) { + case INTEGER: + Object f = field.get(o); + if (f instanceof Boolean) { + return String.valueOf(((boolean) field.get(o)) ? 1 : 0); + } else { + return String.valueOf((int) field.get(o)); + } + case TEXT: + return "\"" + field.get(o) + "" + "\""; + case DOUBLE: + return String.valueOf((double) field.get(o)); + case FLOAT: + return String.valueOf((float) field.get(o)); + case BIGINT: + return String.valueOf((long) field.get(o)); + } + return null; + } + + /** + * 根据数据类型将对象转换为String + * + * @param dataType + * @param o + * @return + */ + public static String valueToString(DatabaseDataType dataType, Object o) { + switch (dataType) { + case INTEGER: + if (o instanceof Boolean) { + return ((boolean) o) ? "1" : "0"; + } else { + return String.valueOf((int) o); + } + case TEXT: + return "\"" + o + "\""; + case DOUBLE: + return String.valueOf((double) o); + case FLOAT: + return String.valueOf((float) o); + case BIGINT: + return String.valueOf((long) o); + } + return null; + } } diff --git a/app/src/main/java/io/neoterm/framework/database/annotation/ID.java b/app/src/main/java/io/neoterm/framework/database/annotation/ID.java index 9c157e1..5b29596 100644 --- a/app/src/main/java/io/neoterm/framework/database/annotation/ID.java +++ b/app/src/main/java/io/neoterm/framework/database/annotation/ID.java @@ -11,10 +11,10 @@ import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ID { - /** - * 只对Integer类型的ID字段有效 - * - * @return 是否为自增长 - */ - boolean autoIncrement() default false; + /** + * 只对Integer类型的ID字段有效 + * + * @return 是否为自增长 + */ + boolean autoIncrement() default false; } diff --git a/app/src/main/java/io/neoterm/framework/database/annotation/Table.java b/app/src/main/java/io/neoterm/framework/database/annotation/Table.java index c8869e0..db54028 100644 --- a/app/src/main/java/io/neoterm/framework/database/annotation/Table.java +++ b/app/src/main/java/io/neoterm/framework/database/annotation/Table.java @@ -11,13 +11,13 @@ import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Table { - /** - * @return 表名 - */ - String name() default ""; + /** + * @return 表名 + */ + String name() default ""; - /** - * @return 在表创建后需要回调的方法 - */ - String afterTableCreate() default ""; + /** + * @return 在表创建后需要回调的方法 + */ + String afterTableCreate() default ""; } diff --git a/app/src/main/java/io/neoterm/framework/database/bean/TableInfo.java b/app/src/main/java/io/neoterm/framework/database/bean/TableInfo.java index 8547a24..d9d28dd 100644 --- a/app/src/main/java/io/neoterm/framework/database/bean/TableInfo.java +++ b/app/src/main/java/io/neoterm/framework/database/bean/TableInfo.java @@ -1,46 +1,46 @@ package io.neoterm.framework.database.bean; +import io.neoterm.framework.database.DatabaseDataType; + import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; -import io.neoterm.framework.database.DatabaseDataType; - /** * @author kiva */ public class TableInfo { - /** - * 是否包含ID - */ - public boolean containID; - /** - * 主键字段 - */ - public Field primaryField; + /** + * 是否包含ID + */ + public boolean containID; + /** + * 主键字段 + */ + public Field primaryField; - /** - * 表名 - */ - public String tableName; + /** + * 表名 + */ + public String tableName; - /** - * 字段表 - */ - public Map fieldToDataTypeMap; + /** + * 字段表 + */ + public Map fieldToDataTypeMap; - /** - * 创建table的语句 - */ - public String createTableStatement; + /** + * 创建table的语句 + */ + public String createTableStatement; - /** - * 是否已经创建 - */ - public boolean isCreate = false; + /** + * 是否已经创建 + */ + public boolean isCreate = false; - public Method afterTableCreateMethod; + public Method afterTableCreateMethod; } diff --git a/app/src/main/java/io/neoterm/framework/reflection/NullPointer.java b/app/src/main/java/io/neoterm/framework/reflection/NullPointer.java index 6a88d86..18d4a70 100644 --- a/app/src/main/java/io/neoterm/framework/reflection/NullPointer.java +++ b/app/src/main/java/io/neoterm/framework/reflection/NullPointer.java @@ -2,6 +2,7 @@ package io.neoterm.framework.reflection; /** * class representing null pointer. + * * @author kiva */ public class NullPointer { diff --git a/app/src/main/java/io/neoterm/framework/reflection/Reflect.java b/app/src/main/java/io/neoterm/framework/reflection/Reflect.java index c3494ad..92ae395 100644 --- a/app/src/main/java/io/neoterm/framework/reflection/Reflect.java +++ b/app/src/main/java/io/neoterm/framework/reflection/Reflect.java @@ -11,550 +11,550 @@ import java.util.Map; * @author kiva */ public class Reflect { - private final Object mObject; - private final boolean isClass; + private final Object mObject; + private final boolean isClass; - private Reflect(Class type) { - this.mObject = type; - this.isClass = true; + private Reflect(Class type) { + this.mObject = type; + this.isClass = true; + } + + private Reflect(Object object) { + this.mObject = object; + this.isClass = false; + } + + /** + * Create reflector from class name. + * + * @param name Full class name + * @return Reflector + * @throws ReflectionException If any error occurs + * @see #on(Class) + */ + public static Reflect on(String name) throws ReflectionException { + return on(forName(name)); + } + + /** + * Create reflector from class name using given class loader. + * + * @param name Full class name + * @param classLoader Given class loader + * @return Reflector + * @throws ReflectionException If any error occurs + * @see #on(Class) + */ + public static Reflect on(String name, ClassLoader classLoader) throws ReflectionException { + return on(forName(name, classLoader)); + } + + /** + * Create reflector from given class type. + * Helpful especially when you want to access static fields. + * + * @param clazz Given class type + * @return Reflector + */ + public static Reflect on(Class clazz) { + return new Reflect(clazz); + } + + /** + * Wrap an object and return its reflector.

+ * Helpful especially when you want to access instance fields and methods on any {@link Object} + * + * @param object The object to be wrapped + * @return Reflector + */ + public static Reflect on(Object object) { + return new Reflect(object); + } + + private static Reflect on(Method method, Object receiver, Object... args) throws ReflectionException { + try { + makeAccessible(method); + + if (method.getReturnType() == void.class) { + method.invoke(receiver, args); + return on(receiver); + } else { + return on(method.invoke(receiver, args)); + } + } catch (Exception e) { + throw new ReflectionException(e); + } + } + + /** + * Make an {@link AccessibleObject} accessible. + * + * @param accessible + * @param + * @return + */ + public static T makeAccessible(T accessible) { + if (accessible == null) { + return null; } - private Reflect(Object object) { - this.mObject = object; - this.isClass = false; - } + if (accessible instanceof Member) { + Member member = (Member) accessible; - /** - * Create reflector from class name. - * - * @param name Full class name - * @return Reflector - * @throws ReflectionException If any error occurs - * @see #on(Class) - */ - public static Reflect on(String name) throws ReflectionException { - return on(forName(name)); - } - - /** - * Create reflector from class name using given class loader. - * - * @param name Full class name - * @param classLoader Given class loader - * @return Reflector - * @throws ReflectionException If any error occurs - * @see #on(Class) - */ - public static Reflect on(String name, ClassLoader classLoader) throws ReflectionException { - return on(forName(name, classLoader)); - } - - /** - * Create reflector from given class type. - * Helpful especially when you want to access static fields. - * - * @param clazz Given class type - * @return Reflector - */ - public static Reflect on(Class clazz) { - return new Reflect(clazz); - } - - /** - * Wrap an object and return its reflector.

- * Helpful especially when you want to access instance fields and methods on any {@link Object} - * - * @param object The object to be wrapped - * @return Reflector - */ - public static Reflect on(Object object) { - return new Reflect(object); - } - - private static Reflect on(Method method, Object receiver, Object... args) throws ReflectionException { - try { - makeAccessible(method); - - if (method.getReturnType() == void.class) { - method.invoke(receiver, args); - return on(receiver); - } else { - return on(method.invoke(receiver, args)); - } - } catch (Exception e) { - throw new ReflectionException(e); - } - } - - /** - * Make an {@link AccessibleObject} accessible. - * - * @param accessible - * @param - * @return - */ - public static T makeAccessible(T accessible) { - if (accessible == null) { - return null; - } - - if (accessible instanceof Member) { - Member member = (Member) accessible; - - if (Modifier.isPublic(member.getModifiers()) && - Modifier.isPublic(member.getDeclaringClass().getModifiers())) { - - return accessible; - } - } - - if (!accessible.isAccessible()) { - accessible.setAccessible(true); - } + if (Modifier.isPublic(member.getModifiers()) && + Modifier.isPublic(member.getDeclaringClass().getModifiers())) { return accessible; + } } - private static String property(String string) { - int length = string.length(); - - if (length == 0) { - return ""; - } else if (length == 1) { - return string.toLowerCase(); - } else { - return string.substring(0, 1).toLowerCase() + string.substring(1); - } + if (!accessible.isAccessible()) { + accessible.setAccessible(true); } - private static Reflect on(Constructor constructor, Object... args) throws ReflectionException { + return accessible; + } + + private static String property(String string) { + int length = string.length(); + + if (length == 0) { + return ""; + } else if (length == 1) { + return string.toLowerCase(); + } else { + return string.substring(0, 1).toLowerCase() + string.substring(1); + } + } + + private static Reflect on(Constructor constructor, Object... args) throws ReflectionException { + try { + return on(makeAccessible(constructor).newInstance(args)); + } catch (Exception e) { + throw new ReflectionException(e); + } + } + + /** + * If we are wrapping another reflector, get its real object. + */ + private static Object unwrap(Object object) { + if (object instanceof Reflect) { + return ((Reflect) object).get(); + } + + return object; + } + + /** + * Convert object arrays into elements' class type arrays. + * If encountered {@code null}, use {@link NullPointer}'s class type instead. + * + * @see Object#getClass() + */ + private static Class[] convertTypes(Object... values) { + if (values == null) { + return new Class[0]; + } + + Class[] result = new Class[values.length]; + + for (int i = 0; i < values.length; i++) { + Object value = values[i]; + result[i] = value == null ? NullPointer.class : value.getClass(); + } + + return result; + } + + /** + * Get a class type of a class, which may cause its static-initialization + * + * @see Class#forName(String) + */ + private static Class forName(String name) throws ReflectionException { + try { + return Class.forName(name); + } catch (Exception e) { + throw new ReflectionException(e); + } + } + + private static Class forName(String name, ClassLoader classLoader) throws ReflectionException { + try { + return Class.forName(name, true, classLoader); + } catch (Exception e) { + throw new ReflectionException(e); + } + } + + /** + * Wrap primitive class types into object class types. + * + * @param type Class type that may be primitive class type + * @return Wrapped class type + */ + private static Class wrapClassType(Class type) { + if (type == null) { + return null; + } else if (type.isPrimitive()) { + if (boolean.class == type) { + return Boolean.class; + } else if (int.class == type) { + return Integer.class; + } else if (long.class == type) { + return Long.class; + } else if (short.class == type) { + return Short.class; + } else if (byte.class == type) { + return Byte.class; + } else if (double.class == type) { + return Double.class; + } else if (float.class == type) { + return Float.class; + } else if (char.class == type) { + return Character.class; + } else if (void.class == type) { + return Void.class; + } + } + + return type; + } + + /** + * Get the real object that reflector operates. + * + * @param The type of the real object. + * @return The real object. + */ + @SuppressWarnings("unchecked") + public T get() { + return (T) mObject; + } + + /** + * Set a field to given value. + * + * @param name Field name + * @param value New value + * @return Reflector + * @throws ReflectionException If any error occurs + */ + public Reflect set(String name, Object value) throws ReflectionException { + try { + Field field = lookupField(name); + field.setAccessible(true); + field.set(mObject, unwrap(value)); + return this; + } catch (Exception e) { + throw new ReflectionException(e); + } + } + + /** + * Get the value of given field + * + * @param name Field name + * @param The type of value + * @return Value + * @throws ReflectionException If any error occurs + */ + public T get(String name) throws ReflectionException { + return field(name).get(); + } + + /** + * Get field by name. + * + * @param name Field name + * @return {@link Field} + * @throws ReflectionException If any error occurs + */ + public Reflect field(String name) throws ReflectionException { + try { + Field field = lookupField(name); + return on(field.get(mObject)); + } catch (Exception e) { + throw new ReflectionException(e); + } + } + + private Field lookupField(String name) throws ReflectionException { + Class type = type(); + + // 先尝试取得公有字段 + try { + return type.getField(name); + } + + //此时尝试非公有字段 + catch (NoSuchFieldException e) { + do { try { - return on(makeAccessible(constructor).newInstance(args)); - } catch (Exception e) { - throw new ReflectionException(e); + return makeAccessible(type.getDeclaredField(name)); + } catch (NoSuchFieldException ignore) { } + + type = type.getSuperclass(); + } + while (type != null); + + throw new ReflectionException(e); } + } - /** - * If we are wrapping another reflector, get its real object. - */ - private static Object unwrap(Object object) { - if (object instanceof Reflect) { - return ((Reflect) object).get(); + /** + * Load all fields into a map, the key is field name and the value is its reflector. + * + * @return Map to all fields. + */ + public Map fields() { + Map result = new LinkedHashMap(); + Class type = type(); + + do { + for (Field field : type.getDeclaredFields()) { + if (!isClass ^ Modifier.isStatic(field.getModifiers())) { + String name = field.getName(); + + if (!result.containsKey(name)) + result.put(name, field(name)); } + } - return object; + type = type.getSuperclass(); } + while (type != null); - /** - * Convert object arrays into elements' class type arrays. - * If encountered {@code null}, use {@link NullPointer}'s class type instead. - * - * @see Object#getClass() - */ - private static Class[] convertTypes(Object... values) { - if (values == null) { - return new Class[0]; - } + return result; + } - Class[] result = new Class[values.length]; + /** + * Call a method by name without parameters. + * + * @param name Method name + * @return Reflector to the return value of the method + * @throws ReflectionException If any error occurs + */ + public Reflect call(String name) throws ReflectionException { + return call(name, new Object[0]); + } - for (int i = 0; i < values.length; i++) { - Object value = values[i]; - result[i] = value == null ? NullPointer.class : value.getClass(); - } + /** + * Call a method by name and parameters. + * + * @param name Method name + * @param args Parameters + * @return Reflector to the return value of the method + * @throws ReflectionException If any error occurs + */ + public Reflect call(String name, Object... args) throws ReflectionException { + Class[] types = convertTypes(args); - return result; + try { + Method method = exactMethod(name, types); + return on(method, mObject, args); + } catch (NoSuchMethodException e) { + try { + Method method = lookupSimilarMethod(name, types); + return on(method, mObject, args); + } catch (NoSuchMethodException e1) { + throw new ReflectionException(e1); + } } + } - /** - * Get a class type of a class, which may cause its static-initialization - * - * @see Class#forName(String) - */ - private static Class forName(String name) throws ReflectionException { + private Method exactMethod(String name, Class[] types) throws NoSuchMethodException { + Class type = type(); + + try { + return type.getMethod(name, types); + } catch (NoSuchMethodException e) { + do { try { - return Class.forName(name); - } catch (Exception e) { - throw new ReflectionException(e); + return type.getDeclaredMethod(name, types); + } catch (NoSuchMethodException ignore) { } + + type = type.getSuperclass(); + } + while (type != null); + + throw new NoSuchMethodException(); + } + } + + /** + * Find a method that is similar to the wanted one. + */ + private Method lookupSimilarMethod(String name, Class[] types) throws NoSuchMethodException { + Class type = type(); + + for (Method method : type.getMethods()) { + if (isSignatureSimilar(method, name, types)) { + return method; + } } - private static Class forName(String name, ClassLoader classLoader) throws ReflectionException { - try { - return Class.forName(name, true, classLoader); - } catch (Exception e) { - throw new ReflectionException(e); + do { + for (Method method : type.getDeclaredMethods()) { + if (isSignatureSimilar(method, name, types)) { + return method; } - } + } - /** - * Wrap primitive class types into object class types. - * - * @param type Class type that may be primitive class type - * @return Wrapped class type - */ - private static Class wrapClassType(Class type) { - if (type == null) { + type = type.getSuperclass(); + } + while (type != null); + + throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays.toString(types) + " could be found on type " + type() + "."); + } + + private boolean isSignatureSimilar(Method possiblyMatchingMethod, + String wantedMethodName, + Class[] wantedParamTypes) { + return possiblyMatchingMethod.getName().equals(wantedMethodName) + && match(possiblyMatchingMethod.getParameterTypes(), wantedParamTypes); + } + + /** + * Create an instance using its default constructor. + * + * @return Reflector to the return value of the method + * @throws ReflectionException If any error occurs + */ + public Reflect create() throws ReflectionException { + return create(new Object[0]); + } + + /** + * Create an instance by parameters. + * + * @param args Parameters + * @return Reflector to the return value of the method + * @throws ReflectionException If any error occurs + */ + public Reflect create(Object... args) throws ReflectionException { + Class[] types = convertTypes(args); + + + try { + Constructor constructor = type().getDeclaredConstructor(types); + return on(constructor, args); + } catch (NoSuchMethodException e) { + for (Constructor constructor : type().getDeclaredConstructors()) { + if (match(constructor.getParameterTypes(), types)) { + return on(constructor, args); + } + } + + throw new ReflectionException(e); + } + } + + /** + * Create a dynamic proxy based on the given type. + * If we are maintaining a Map and error occurs when calling methods, + * we will return value from Map as return value. + * Helpful especially when creating default data handlers. + * + * @param proxyType The type to be proxy-ed + * @return Proxy object + */ + @SuppressWarnings("unchecked") + public

P as(Class

proxyType) { + final boolean isMap = (mObject instanceof Map); + final InvocationHandler handler = (proxy, method, args) -> { + String name = method.getName(); + try { + return on(mObject).call(name, args).get(); + } catch (ReflectionException e) { + if (isMap) { + Map map = (Map) mObject; + int length = (args == null ? 0 : args.length); + + // Pay special attention to those getters and setters + if (length == 0 && name.startsWith("get")) { + return map.get(property(name.substring(3))); + } else if (length == 0 && name.startsWith("is")) { + return map.get(property(name.substring(2))); + } else if (length == 1 && name.startsWith("set")) { + map.put(property(name.substring(3)), args[0]); return null; - } else if (type.isPrimitive()) { - if (boolean.class == type) { - return Boolean.class; - } else if (int.class == type) { - return Integer.class; - } else if (long.class == type) { - return Long.class; - } else if (short.class == type) { - return Short.class; - } else if (byte.class == type) { - return Byte.class; - } else if (double.class == type) { - return Double.class; - } else if (float.class == type) { - return Float.class; - } else if (char.class == type) { - return Character.class; - } else if (void.class == type) { - return Void.class; - } + } } - return type; - } + throw e; + } + }; - /** - * Get the real object that reflector operates. - * - * @param The type of the real object. - * @return The real object. - */ - @SuppressWarnings("unchecked") - public T get() { - return (T) mObject; - } + return (P) Proxy.newProxyInstance(proxyType.getClassLoader(), + new Class[]{proxyType}, handler); + } - /** - * Set a field to given value. - * - * @param name Field name - * @param value New value - * @return Reflector - * @throws ReflectionException If any error occurs - */ - public Reflect set(String name, Object value) throws ReflectionException { - try { - Field field = lookupField(name); - field.setAccessible(true); - field.set(mObject, unwrap(value)); - return this; - } catch (Exception e) { - throw new ReflectionException(e); - } - } - - /** - * Get the value of given field - * - * @param name Field name - * @param The type of value - * @return Value - * @throws ReflectionException If any error occurs - */ - public T get(String name) throws ReflectionException { - return field(name).get(); - } - - /** - * Get field by name. - * - * @param name Field name - * @return {@link Field} - * @throws ReflectionException If any error occurs - */ - public Reflect field(String name) throws ReflectionException { - try { - Field field = lookupField(name); - return on(field.get(mObject)); - } catch (Exception e) { - throw new ReflectionException(e); - } - } - - private Field lookupField(String name) throws ReflectionException { - Class type = type(); - - // 先尝试取得公有字段 - try { - return type.getField(name); + /** + * Check whether types matches to avoid {@link ClassCastException} when calling a method. + * If encountered primitive type, convert to object type first. + */ + private boolean match(Class[] declaredTypes, Class[] actualTypes) { + if (declaredTypes.length == actualTypes.length) { + for (int i = 0; i < actualTypes.length; i++) { + // nulls are acceptable on any occasions + if (actualTypes[i] == NullPointer.class) { + continue; } - //此时尝试非公有字段 - catch (NoSuchFieldException e) { - do { - try { - return makeAccessible(type.getDeclaredField(name)); - } catch (NoSuchFieldException ignore) { - } - - type = type.getSuperclass(); - } - while (type != null); - - throw new ReflectionException(e); + if (wrapClassType(declaredTypes[i]).isAssignableFrom(wrapClassType(actualTypes[i]))) { + continue; } - } - - /** - * Load all fields into a map, the key is field name and the value is its reflector. - * - * @return Map to all fields. - */ - public Map fields() { - Map result = new LinkedHashMap(); - Class type = type(); - - do { - for (Field field : type.getDeclaredFields()) { - if (!isClass ^ Modifier.isStatic(field.getModifiers())) { - String name = field.getName(); - - if (!result.containsKey(name)) - result.put(name, field(name)); - } - } - - type = type.getSuperclass(); - } - while (type != null); - - return result; - } - - /** - * Call a method by name without parameters. - * - * @param name Method name - * @return Reflector to the return value of the method - * @throws ReflectionException If any error occurs - */ - public Reflect call(String name) throws ReflectionException { - return call(name, new Object[0]); - } - - /** - * Call a method by name and parameters. - * - * @param name Method name - * @param args Parameters - * @return Reflector to the return value of the method - * @throws ReflectionException If any error occurs - */ - public Reflect call(String name, Object... args) throws ReflectionException { - Class[] types = convertTypes(args); - - try { - Method method = exactMethod(name, types); - return on(method, mObject, args); - } catch (NoSuchMethodException e) { - try { - Method method = lookupSimilarMethod(name, types); - return on(method, mObject, args); - } catch (NoSuchMethodException e1) { - throw new ReflectionException(e1); - } - } - } - - private Method exactMethod(String name, Class[] types) throws NoSuchMethodException { - Class type = type(); - - try { - return type.getMethod(name, types); - } catch (NoSuchMethodException e) { - do { - try { - return type.getDeclaredMethod(name, types); - } catch (NoSuchMethodException ignore) { - } - - type = type.getSuperclass(); - } - while (type != null); - - throw new NoSuchMethodException(); - } - } - - /** - * Find a method that is similar to the wanted one. - */ - private Method lookupSimilarMethod(String name, Class[] types) throws NoSuchMethodException { - Class type = type(); - - for (Method method : type.getMethods()) { - if (isSignatureSimilar(method, name, types)) { - return method; - } - } - - do { - for (Method method : type.getDeclaredMethods()) { - if (isSignatureSimilar(method, name, types)) { - return method; - } - } - - type = type.getSuperclass(); - } - while (type != null); - - throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays.toString(types) + " could be found on type " + type() + "."); - } - - private boolean isSignatureSimilar(Method possiblyMatchingMethod, - String wantedMethodName, - Class[] wantedParamTypes) { - return possiblyMatchingMethod.getName().equals(wantedMethodName) - && match(possiblyMatchingMethod.getParameterTypes(), wantedParamTypes); - } - - /** - * Create an instance using its default constructor. - * - * @return Reflector to the return value of the method - * @throws ReflectionException If any error occurs - */ - public Reflect create() throws ReflectionException { - return create(new Object[0]); - } - - /** - * Create an instance by parameters. - * - * @param args Parameters - * @return Reflector to the return value of the method - * @throws ReflectionException If any error occurs - */ - public Reflect create(Object... args) throws ReflectionException { - Class[] types = convertTypes(args); - - - try { - Constructor constructor = type().getDeclaredConstructor(types); - return on(constructor, args); - } catch (NoSuchMethodException e) { - for (Constructor constructor : type().getDeclaredConstructors()) { - if (match(constructor.getParameterTypes(), types)) { - return on(constructor, args); - } - } - - throw new ReflectionException(e); - } - } - - /** - * Create a dynamic proxy based on the given type. - * If we are maintaining a Map and error occurs when calling methods, - * we will return value from Map as return value. - * Helpful especially when creating default data handlers. - * - * @param proxyType The type to be proxy-ed - * @return Proxy object - */ - @SuppressWarnings("unchecked") - public

P as(Class

proxyType) { - final boolean isMap = (mObject instanceof Map); - final InvocationHandler handler = (proxy, method, args) -> { - String name = method.getName(); - try { - return on(mObject).call(name, args).get(); - } catch (ReflectionException e) { - if (isMap) { - Map map = (Map) mObject; - int length = (args == null ? 0 : args.length); - - // Pay special attention to those getters and setters - if (length == 0 && name.startsWith("get")) { - return map.get(property(name.substring(3))); - } else if (length == 0 && name.startsWith("is")) { - return map.get(property(name.substring(2))); - } else if (length == 1 && name.startsWith("set")) { - map.put(property(name.substring(3)), args[0]); - return null; - } - } - - throw e; - } - }; - - return (P) Proxy.newProxyInstance(proxyType.getClassLoader(), - new Class[]{proxyType}, handler); - } - - /** - * Check whether types matches to avoid {@link ClassCastException} when calling a method. - * If encountered primitive type, convert to object type first. - */ - private boolean match(Class[] declaredTypes, Class[] actualTypes) { - if (declaredTypes.length == actualTypes.length) { - for (int i = 0; i < actualTypes.length; i++) { - // nulls are acceptable on any occasions - if (actualTypes[i] == NullPointer.class) { - continue; - } - - if (wrapClassType(declaredTypes[i]).isAssignableFrom(wrapClassType(actualTypes[i]))) { - continue; - } - return false; - } - - return true; - } else { - return false; - } - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return mObject.hashCode(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object obj) { - if (obj instanceof Reflect) { - return mObject.equals(((Reflect) obj).get()); - } - return false; + } + + return true; + } else { + return false; + } + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return mObject.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof Reflect) { + return mObject.equals(((Reflect) obj).get()); } - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return mObject.toString(); - } + return false; + } - /** - * Get the class type of the real object that reflector operates. - * - * @see Object#getClass() - */ - public Class type() { - if (isClass) { - return (Class) mObject; - } else { - return mObject.getClass(); - } + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return mObject.toString(); + } + + /** + * Get the class type of the real object that reflector operates. + * + * @see Object#getClass() + */ + public Class type() { + if (isClass) { + return (Class) mObject; + } else { + return mObject.getClass(); } + } } diff --git a/app/src/main/java/io/neoterm/framework/reflection/ReflectionException.java b/app/src/main/java/io/neoterm/framework/reflection/ReflectionException.java index ee8241f..e2cffb4 100644 --- a/app/src/main/java/io/neoterm/framework/reflection/ReflectionException.java +++ b/app/src/main/java/io/neoterm/framework/reflection/ReflectionException.java @@ -4,7 +4,7 @@ package io.neoterm.framework.reflection; * @author kiva */ public class ReflectionException extends RuntimeException { - ReflectionException(Throwable cause) { - super(cause); - } + ReflectionException(Throwable cause) { + super(cause); + } } diff --git a/app/src/main/java/io/neoterm/frontend/completion/CompletionManager.kt b/app/src/main/java/io/neoterm/frontend/completion/CompletionManager.kt index 2260646..a7d69ca 100644 --- a/app/src/main/java/io/neoterm/frontend/completion/CompletionManager.kt +++ b/app/src/main/java/io/neoterm/frontend/completion/CompletionManager.kt @@ -7,34 +7,34 @@ import io.neoterm.frontend.completion.provider.ICandidateProvider * @author kiva */ object CompletionManager { - private val candidateProviders = mutableMapOf() + private val candidateProviders = mutableMapOf() - fun registerProvider(provider: ICandidateProvider) { - this.candidateProviders[provider.providerName] = provider - } + fun registerProvider(provider: ICandidateProvider) { + this.candidateProviders[provider.providerName] = provider + } - fun unregisterProvider(providerName: String) { - this.candidateProviders.remove(providerName) - } + fun unregisterProvider(providerName: String) { + this.candidateProviders.remove(providerName) + } - fun unregisterProvider(provider: ICandidateProvider) { - unregisterProvider(provider.providerName) - } + fun unregisterProvider(provider: ICandidateProvider) { + unregisterProvider(provider.providerName) + } - fun getProvider(providerName: String): ICandidateProvider? { - return candidateProviders[providerName] - } + fun getProvider(providerName: String): ICandidateProvider? { + return candidateProviders[providerName] + } - fun tryCompleteFor(text: String): CompletionResult { - val detector = detectProviders(text) - val provider = detector.detectBest() + fun tryCompleteFor(text: String): CompletionResult { + val detector = detectProviders(text) + val provider = detector.detectBest() - val candidates = provider?.provideCandidates(text) ?: listOf() - return CompletionResult(candidates, detector) - } + val candidates = provider?.provideCandidates(text) ?: listOf() + return CompletionResult(candidates, detector) + } - private fun detectProviders(text: String): ProviderDetector { - return ProviderDetector(candidateProviders.values - .takeWhile { it.canComplete(text) }) - } + private fun detectProviders(text: String): ProviderDetector { + return ProviderDetector(candidateProviders.values + .takeWhile { it.canComplete(text) }) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/completion/ProviderDetector.kt b/app/src/main/java/io/neoterm/frontend/completion/ProviderDetector.kt index e2414d8..9bc9268 100644 --- a/app/src/main/java/io/neoterm/frontend/completion/ProviderDetector.kt +++ b/app/src/main/java/io/neoterm/frontend/completion/ProviderDetector.kt @@ -7,19 +7,19 @@ import io.neoterm.frontend.completion.provider.ICandidateProvider * @author kiva */ class ProviderDetector(val providers: List) : MarkScoreListener { - private var detectedProvider: ICandidateProvider? = null + private var detectedProvider: ICandidateProvider? = null - override fun onMarkScore(score: Int) { - // TODO: Save provider score - } + override fun onMarkScore(score: Int) { + // TODO: Save provider score + } - fun detectBest(): ICandidateProvider? { - // TODO: detect best - detectedProvider = if (providers.isEmpty()) - null - else - providers[0] + fun detectBest(): ICandidateProvider? { + // TODO: detect best + detectedProvider = if (providers.isEmpty()) + null + else + providers[0] - return detectedProvider - } + return detectedProvider + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/completion/listener/MarkScoreListener.kt b/app/src/main/java/io/neoterm/frontend/completion/listener/MarkScoreListener.kt index eb0c0f4..2e8c1d2 100644 --- a/app/src/main/java/io/neoterm/frontend/completion/listener/MarkScoreListener.kt +++ b/app/src/main/java/io/neoterm/frontend/completion/listener/MarkScoreListener.kt @@ -5,5 +5,5 @@ package io.neoterm.frontend.completion.listener */ interface MarkScoreListener { - fun onMarkScore(score: Int) + fun onMarkScore(score: Int) } diff --git a/app/src/main/java/io/neoterm/frontend/completion/listener/OnAutoCompleteListener.kt b/app/src/main/java/io/neoterm/frontend/completion/listener/OnAutoCompleteListener.kt index 26f0f4f..feb7c8e 100755 --- a/app/src/main/java/io/neoterm/frontend/completion/listener/OnAutoCompleteListener.kt +++ b/app/src/main/java/io/neoterm/frontend/completion/listener/OnAutoCompleteListener.kt @@ -6,11 +6,11 @@ package io.neoterm.frontend.completion.listener * @version 1.0 */ interface OnAutoCompleteListener { - fun onCompletionRequired(newText: String?) + fun onCompletionRequired(newText: String?) - fun onKeyCode(keyCode: Int, keyMod: Int) + fun onKeyCode(keyCode: Int, keyMod: Int) - fun onCleanUp() + fun onCleanUp() - fun onFinishCompletion(): Boolean + fun onFinishCompletion(): Boolean } diff --git a/app/src/main/java/io/neoterm/frontend/completion/listener/OnCandidateSelectedListener.kt b/app/src/main/java/io/neoterm/frontend/completion/listener/OnCandidateSelectedListener.kt index 8d349f7..89644f4 100644 --- a/app/src/main/java/io/neoterm/frontend/completion/listener/OnCandidateSelectedListener.kt +++ b/app/src/main/java/io/neoterm/frontend/completion/listener/OnCandidateSelectedListener.kt @@ -6,5 +6,5 @@ import io.neoterm.frontend.completion.model.CompletionCandidate * @author kiva */ interface OnCandidateSelectedListener { - fun onCandidateSelected(candidate: CompletionCandidate) + fun onCandidateSelected(candidate: CompletionCandidate) } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/completion/model/CompletionCandidate.kt b/app/src/main/java/io/neoterm/frontend/completion/model/CompletionCandidate.kt index e6b41aa..e819a26 100644 --- a/app/src/main/java/io/neoterm/frontend/completion/model/CompletionCandidate.kt +++ b/app/src/main/java/io/neoterm/frontend/completion/model/CompletionCandidate.kt @@ -4,6 +4,6 @@ package io.neoterm.frontend.completion.model * @author kiva */ class CompletionCandidate(var completeString: String) { - var displayName: String = completeString - var description: String? = null + var displayName: String = completeString + var description: String? = null } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/completion/model/CompletionResult.kt b/app/src/main/java/io/neoterm/frontend/completion/model/CompletionResult.kt index 62d088a..6069191 100644 --- a/app/src/main/java/io/neoterm/frontend/completion/model/CompletionResult.kt +++ b/app/src/main/java/io/neoterm/frontend/completion/model/CompletionResult.kt @@ -6,11 +6,11 @@ import io.neoterm.frontend.completion.listener.MarkScoreListener * @author kiva */ class CompletionResult(val candidates: List, var scoreMarker: MarkScoreListener) { - fun markScore(score: Int) { - scoreMarker.onMarkScore(score) - } + fun markScore(score: Int) { + scoreMarker.onMarkScore(score) + } - fun hasResult(): Boolean { - return candidates.isNotEmpty() - } + fun hasResult(): Boolean { + return candidates.isNotEmpty() + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/completion/provider/ICandidateProvider.kt b/app/src/main/java/io/neoterm/frontend/completion/provider/ICandidateProvider.kt index 30e36a3..e3a39b7 100644 --- a/app/src/main/java/io/neoterm/frontend/completion/provider/ICandidateProvider.kt +++ b/app/src/main/java/io/neoterm/frontend/completion/provider/ICandidateProvider.kt @@ -7,9 +7,9 @@ import io.neoterm.frontend.completion.model.CompletionCandidate */ interface ICandidateProvider { - val providerName: String + val providerName: String - fun provideCandidates(text: String): List? + fun provideCandidates(text: String): List? - fun canComplete(text: String): Boolean + fun canComplete(text: String): Boolean } diff --git a/app/src/main/java/io/neoterm/frontend/completion/view/CandidatePopupWindow.kt b/app/src/main/java/io/neoterm/frontend/completion/view/CandidatePopupWindow.kt index dd212f8..3c06157 100644 --- a/app/src/main/java/io/neoterm/frontend/completion/view/CandidatePopupWindow.kt +++ b/app/src/main/java/io/neoterm/frontend/completion/view/CandidatePopupWindow.kt @@ -21,124 +21,126 @@ import io.neoterm.frontend.terminal.TerminalView * @author kiva */ class CandidatePopupWindow(val context: Context) { - var candidates: List? = null - var onCandidateSelectedListener: OnCandidateSelectedListener? = null + var candidates: List? = null + var onCandidateSelectedListener: OnCandidateSelectedListener? = null - private var popupWindow: PopupWindow? = null - private var wantsToFinish = false - private var candidateAdapter: CandidateAdapter? = null - private var candidateListView: ListView? = null + private var popupWindow: PopupWindow? = null + private var wantsToFinish = false + private var candidateAdapter: CandidateAdapter? = null + private var candidateListView: ListView? = null - fun show(terminalView: TerminalView) { - if (popupWindow == null && !wantsToFinish) { - popupWindow = createPopupWindow() - } - - candidateAdapter?.notifyDataSetChanged() - - val popWindow = popupWindow - if (popWindow != null) { - // Ensure that the popup window will not cover the IME. - val rootView = popWindow.contentView - if (rootView is MaxHeightView) { - val maxHeight = terminalView.height - rootView.setMaxHeight(maxHeight) - } - - popWindow.showAtLocation(terminalView, Gravity.BOTTOM.and(Gravity.START), - terminalView.cursorAbsoluteX, - terminalView.cursorAbsoluteY) - } + fun show(terminalView: TerminalView) { + if (popupWindow == null && !wantsToFinish) { + popupWindow = createPopupWindow() } - fun dismiss() { - popupWindow?.dismiss() + candidateAdapter?.notifyDataSetChanged() + + val popWindow = popupWindow + if (popWindow != null) { + // Ensure that the popup window will not cover the IME. + val rootView = popWindow.contentView + if (rootView is MaxHeightView) { + val maxHeight = terminalView.height + rootView.setMaxHeight(maxHeight) + } + + popWindow.showAtLocation( + terminalView, Gravity.BOTTOM.and(Gravity.START), + terminalView.cursorAbsoluteX, + terminalView.cursorAbsoluteY + ) } + } - fun isShowing(): Boolean { - return popupWindow?.isShowing ?: false - } + fun dismiss() { + popupWindow?.dismiss() + } - private fun createPopupWindow(): PopupWindow { - val popupWindow = PopupWindow(context) - popupWindow.isOutsideTouchable = true - popupWindow.isTouchable = true - val contentView = LayoutInflater.from(context).inflate(R.layout.popup_auto_complete, null, false) - val listView = contentView.findViewById(R.id.popup_complete_candidate_list) - candidateAdapter = CandidateAdapter(this) - listView.adapter = candidateAdapter - listView.setOnItemClickListener({ _, _, position, _ -> - val selectedItem = candidates?.get(position) - if (selectedItem != null) { - onCandidateSelectedListener?.onCandidateSelected(selectedItem) - } - }) + fun isShowing(): Boolean { + return popupWindow?.isShowing ?: false + } - candidateListView = listView - popupWindow.contentView = contentView - return popupWindow - } + private fun createPopupWindow(): PopupWindow { + val popupWindow = PopupWindow(context) + popupWindow.isOutsideTouchable = true + popupWindow.isTouchable = true + val contentView = LayoutInflater.from(context).inflate(R.layout.popup_auto_complete, null, false) + val listView = contentView.findViewById(R.id.popup_complete_candidate_list) + candidateAdapter = CandidateAdapter(this) + listView.adapter = candidateAdapter + listView.setOnItemClickListener({ _, _, position, _ -> + val selectedItem = candidates?.get(position) + if (selectedItem != null) { + onCandidateSelectedListener?.onCandidateSelected(selectedItem) + } + }) - fun cleanup() { - wantsToFinish = true - popupWindow = null - candidateListView = null - candidateAdapter = null - candidates = null - } + candidateListView = listView + popupWindow.contentView = contentView + return popupWindow + } - class CandidateAdapter(val candidatePopupWindow: CandidatePopupWindow) : BaseAdapter() { - override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { - var convertView = convertView - val viewHolder: CandidateViewHolder = - if (convertView != null) { - convertView.tag as CandidateViewHolder - } else { - convertView = LayoutInflater.from(candidatePopupWindow.context) - .inflate(R.layout.item_complete_candidate, null, false) - val viewHolder = CandidateViewHolder(convertView) - convertView.tag = viewHolder - viewHolder - } + fun cleanup() { + wantsToFinish = true + popupWindow = null + candidateListView = null + candidateAdapter = null + candidates = null + } - val candidate = getItem(position) as CompletionCandidate - viewHolder.apply { - display.text = candidate.displayName - if (candidate.description != null) { - splitView.visibility = View.VISIBLE - description.visibility = View.VISIBLE - description.text = candidate.description - } else { - splitView.visibility = View.GONE - description.visibility = View.GONE - } - } - return convertView!! + class CandidateAdapter(val candidatePopupWindow: CandidatePopupWindow) : BaseAdapter() { + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { + var convertView = convertView + val viewHolder: CandidateViewHolder = + if (convertView != null) { + convertView.tag as CandidateViewHolder + } else { + convertView = LayoutInflater.from(candidatePopupWindow.context) + .inflate(R.layout.item_complete_candidate, null, false) + val viewHolder = CandidateViewHolder(convertView) + convertView.tag = viewHolder + viewHolder } - override fun getItem(position: Int): Any? { - return candidatePopupWindow.candidates?.get(position) - } - - override fun getItemId(position: Int): Long { - return position.toLong() - } - - override fun getCount(): Int { - return candidatePopupWindow.candidates?.size ?: 0 + val candidate = getItem(position) as CompletionCandidate + viewHolder.apply { + display.text = candidate.displayName + if (candidate.description != null) { + splitView.visibility = View.VISIBLE + description.visibility = View.VISIBLE + description.text = candidate.description + } else { + splitView.visibility = View.GONE + description.visibility = View.GONE } + } + return convertView!! } - class CandidateViewHolder(rootView: View) { - val display: TextView = rootView.findViewById(R.id.complete_display) - val description: TextView = rootView.findViewById(R.id.complete_description) - val splitView: View = rootView.findViewById(R.id.complete_split) - - init { - val colorScheme = ComponentManager.getComponent().getCurrentColorScheme() - val textColor = TerminalColors.parse(colorScheme.foregroundColor) - display.setTextColor(textColor) - description.setTextColor(textColor) - } + override fun getItem(position: Int): Any? { + return candidatePopupWindow.candidates?.get(position) } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + override fun getCount(): Int { + return candidatePopupWindow.candidates?.size ?: 0 + } + } + + class CandidateViewHolder(rootView: View) { + val display: TextView = rootView.findViewById(R.id.complete_display) + val description: TextView = rootView.findViewById(R.id.complete_description) + val splitView: View = rootView.findViewById(R.id.complete_split) + + init { + val colorScheme = ComponentManager.getComponent().getCurrentColorScheme() + val textColor = TerminalColors.parse(colorScheme.foregroundColor) + display.setTextColor(textColor) + description.setTextColor(textColor) + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/completion/view/MaxHeightView.kt b/app/src/main/java/io/neoterm/frontend/completion/view/MaxHeightView.kt index f36271d..49a0356 100644 --- a/app/src/main/java/io/neoterm/frontend/completion/view/MaxHeightView.kt +++ b/app/src/main/java/io/neoterm/frontend/completion/view/MaxHeightView.kt @@ -7,48 +7,50 @@ import android.widget.LinearLayout class MaxHeightView : LinearLayout { - private var maxHeight = -1 + private var maxHeight = -1 - constructor(context: Context) : super(context) {} + constructor(context: Context) : super(context) {} - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {} + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {} - fun setMaxHeight(maxHeight: Int) { - this.maxHeight = maxHeight + fun setMaxHeight(maxHeight: Int) { + this.maxHeight = maxHeight + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + var finalHeightMeasureSpec = heightMeasureSpec + + if (maxHeight > 0) { + val heightMode = View.MeasureSpec.getMode(heightMeasureSpec) + var heightSize = View.MeasureSpec.getSize(heightMeasureSpec) + + if (heightMode == View.MeasureSpec.EXACTLY) { + heightSize = if (heightSize <= maxHeight) + heightSize + else + maxHeight + } + + if (heightMode == View.MeasureSpec.UNSPECIFIED) { + heightSize = if (heightSize <= maxHeight) + heightSize + else + maxHeight + } + if (heightMode == View.MeasureSpec.AT_MOST) { + heightSize = if (heightSize <= maxHeight) + heightSize + else + maxHeight + } + finalHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec( + heightSize, + heightMode + ) } - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - var finalHeightMeasureSpec = heightMeasureSpec - - if (maxHeight > 0) { - val heightMode = View.MeasureSpec.getMode(heightMeasureSpec) - var heightSize = View.MeasureSpec.getSize(heightMeasureSpec) - - if (heightMode == View.MeasureSpec.EXACTLY) { - heightSize = if (heightSize <= maxHeight) - heightSize - else - maxHeight - } - - if (heightMode == View.MeasureSpec.UNSPECIFIED) { - heightSize = if (heightSize <= maxHeight) - heightSize - else - maxHeight - } - if (heightMode == View.MeasureSpec.AT_MOST) { - heightSize = if (heightSize <= maxHeight) - heightSize - else - maxHeight - } - finalHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(heightSize, - heightMode) - } - - super.onMeasure(widthMeasureSpec, finalHeightMeasureSpec) - } + super.onMeasure(widthMeasureSpec, finalHeightMeasureSpec) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/component/ComponentManager.kt b/app/src/main/java/io/neoterm/frontend/component/ComponentManager.kt index 73f6c8e..b4b5f25 100644 --- a/app/src/main/java/io/neoterm/frontend/component/ComponentManager.kt +++ b/app/src/main/java/io/neoterm/frontend/component/ComponentManager.kt @@ -6,40 +6,40 @@ import java.util.concurrent.ConcurrentHashMap * @author kiva */ object ComponentManager { - private val COMPONENTS = ConcurrentHashMap, NeoComponent>() + private val COMPONENTS = ConcurrentHashMap, NeoComponent>() - fun registerComponent(componentClass: Class) { - if (COMPONENTS.containsKey(componentClass)) { - throw ComponentDuplicateException(componentClass.simpleName) - } - val component = createServiceInstance(componentClass) - COMPONENTS.put(componentClass, component) - component.onServiceInit() + fun registerComponent(componentClass: Class) { + if (COMPONENTS.containsKey(componentClass)) { + throw ComponentDuplicateException(componentClass.simpleName) } + val component = createServiceInstance(componentClass) + COMPONENTS.put(componentClass, component) + component.onServiceInit() + } - fun unregisterComponent(componentInterface: Class) { - val component = COMPONENTS[componentInterface] - if (component != null) { - component.onServiceDestroy() - COMPONENTS.remove(componentInterface) - } + fun unregisterComponent(componentInterface: Class) { + val component = COMPONENTS[componentInterface] + if (component != null) { + component.onServiceDestroy() + COMPONENTS.remove(componentInterface) } + } - @Suppress("UNCHECKED_CAST") - fun getComponent(componentInterface: Class, errorThrow: Boolean = true) : T { - val component: NeoComponent = COMPONENTS[componentInterface] ?: - throw ComponentNotFoundException(componentInterface.simpleName) + @Suppress("UNCHECKED_CAST") + fun getComponent(componentInterface: Class, errorThrow: Boolean = true): T { + val component: NeoComponent = + COMPONENTS[componentInterface] ?: throw ComponentNotFoundException(componentInterface.simpleName) - component.onServiceObtained() - return component as T - } + component.onServiceObtained() + return component as T + } - inline fun getComponent(): T { - val componentInterface = T::class.java - return getComponent(componentInterface); - } + inline fun getComponent(): T { + val componentInterface = T::class.java + return getComponent(componentInterface); + } - private fun createServiceInstance(componentInterface: Class): NeoComponent { - return componentInterface.newInstance() - } + private fun createServiceInstance(componentInterface: Class): NeoComponent { + return componentInterface.newInstance() + } } diff --git a/app/src/main/java/io/neoterm/frontend/component/NeoComponent.kt b/app/src/main/java/io/neoterm/frontend/component/NeoComponent.kt index 6a0347a..f54c6d3 100644 --- a/app/src/main/java/io/neoterm/frontend/component/NeoComponent.kt +++ b/app/src/main/java/io/neoterm/frontend/component/NeoComponent.kt @@ -4,7 +4,7 @@ package io.neoterm.frontend.component * @author kiva */ interface NeoComponent { - fun onServiceInit() - fun onServiceDestroy() - fun onServiceObtained() + fun onServiceInit() + fun onServiceDestroy() + fun onServiceObtained() } diff --git a/app/src/main/java/io/neoterm/frontend/component/helper/ConfigFileBasedComponent.kt b/app/src/main/java/io/neoterm/frontend/component/helper/ConfigFileBasedComponent.kt index 527c9cb..2bf248f 100644 --- a/app/src/main/java/io/neoterm/frontend/component/helper/ConfigFileBasedComponent.kt +++ b/app/src/main/java/io/neoterm/frontend/component/helper/ConfigFileBasedComponent.kt @@ -12,52 +12,52 @@ import java.io.FileFilter * @author kiva */ abstract class ConfigFileBasedComponent(protected val baseDir: String) : NeoComponent { - companion object { - private val TAG = ConfigFileBasedComponent::class.java.simpleName + companion object { + private val TAG = ConfigFileBasedComponent::class.java.simpleName - val NEOLANG_FILTER = FileFilter { - it.extension == "nl" - } + val NEOLANG_FILTER = FileFilter { + it.extension == "nl" } + } - open val checkComponentFileWhenObtained = false + open val checkComponentFileWhenObtained = false - override fun onServiceInit() { - val baseDirFile = File(this.baseDir) - if (!baseDirFile.exists()) { - if (!baseDirFile.mkdirs()) { - throw RuntimeException("Cannot create component config directory: ${baseDirFile.absolutePath}") - } - } - onCheckComponentFiles() + override fun onServiceInit() { + val baseDirFile = File(this.baseDir) + if (!baseDirFile.exists()) { + if (!baseDirFile.mkdirs()) { + throw RuntimeException("Cannot create component config directory: ${baseDirFile.absolutePath}") + } } + onCheckComponentFiles() + } - override fun onServiceDestroy() { + override fun onServiceDestroy() { + } + + override fun onServiceObtained() { + if (checkComponentFileWhenObtained) { + onCheckComponentFiles() } + } - override fun onServiceObtained() { - if (checkComponentFileWhenObtained) { - onCheckComponentFiles() - } + fun loadConfigure(file: File): T? { + return try { + val loaderService = ComponentManager.getComponent() + val configure = loaderService.newLoader(file).loadConfigure() + ?: throw RuntimeException("Parse configuration failed.") + + val configVisitor = configure.getVisitor() + val componentObject = onCreateComponentObject(configVisitor) + componentObject.onConfigLoaded(configVisitor) + componentObject + } catch (e: RuntimeException) { + NLog.e(TAG, "Failed to load config: ${file.absolutePath}: ${e.localizedMessage}") + null } + } - fun loadConfigure(file: File): T? { - return try { - val loaderService = ComponentManager.getComponent() - val configure = loaderService.newLoader(file).loadConfigure() - ?: throw RuntimeException("Parse configuration failed.") + abstract fun onCheckComponentFiles() - val configVisitor = configure.getVisitor() - val componentObject = onCreateComponentObject(configVisitor) - componentObject.onConfigLoaded(configVisitor) - componentObject - } catch (e: RuntimeException) { - NLog.e(TAG, "Failed to load config: ${file.absolutePath}: ${e.localizedMessage}") - null - } - } - - abstract fun onCheckComponentFiles() - - abstract fun onCreateComponentObject(configVisitor: ConfigVisitor): T + abstract fun onCreateComponentObject(configVisitor: ConfigVisitor): T } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/component/helper/ConfigFileBasedObject.kt b/app/src/main/java/io/neoterm/frontend/component/helper/ConfigFileBasedObject.kt index b1d7e17..69cbd83 100644 --- a/app/src/main/java/io/neoterm/frontend/component/helper/ConfigFileBasedObject.kt +++ b/app/src/main/java/io/neoterm/frontend/component/helper/ConfigFileBasedObject.kt @@ -6,6 +6,6 @@ import io.neolang.visitor.ConfigVisitor * @author kiva */ interface ConfigFileBasedObject { - @Throws(RuntimeException::class) - fun onConfigLoaded(configVisitor: ConfigVisitor) + @Throws(RuntimeException::class) + fun onConfigLoaded(configVisitor: ConfigVisitor) } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/config/DefaultValues.kt b/app/src/main/java/io/neoterm/frontend/config/DefaultValues.kt index 42164f4..4236d7c 100644 --- a/app/src/main/java/io/neoterm/frontend/config/DefaultValues.kt +++ b/app/src/main/java/io/neoterm/frontend/config/DefaultValues.kt @@ -4,22 +4,22 @@ package io.neoterm.frontend.config * @author kiva */ object DefaultValues { - const val fontSize = 30 + const val fontSize = 30 - const val enableBell = false - const val enableVibrate = false - const val enableExecveWrapper = true - const val enableAutoCompletion = false - const val enableFullScreen = false - const val enableAutoHideToolbar = false - const val enableSwitchNextTab = false - const val enableExtraKeys = true - const val enableExplicitExtraKeysWeight = false - const val enableBackButtonBeMappedToEscape = false - const val enableSpecialVolumeKeys = false - const val enableWordBasedIme = false + const val enableBell = false + const val enableVibrate = false + const val enableExecveWrapper = true + const val enableAutoCompletion = false + const val enableFullScreen = false + const val enableAutoHideToolbar = false + const val enableSwitchNextTab = false + const val enableExtraKeys = true + const val enableExplicitExtraKeysWeight = false + const val enableBackButtonBeMappedToEscape = false + const val enableSpecialVolumeKeys = false + const val enableWordBasedIme = false - const val loginShell = "bash" - const val initialCommand = "" - const val defaultFont = "SourceCodePro" + const val loginShell = "bash" + const val initialCommand = "" + const val defaultFont = "SourceCodePro" } diff --git a/app/src/main/java/io/neoterm/frontend/config/NeoConfigureFile.kt b/app/src/main/java/io/neoterm/frontend/config/NeoConfigureFile.kt index a9b32f7..e0b0651 100644 --- a/app/src/main/java/io/neoterm/frontend/config/NeoConfigureFile.kt +++ b/app/src/main/java/io/neoterm/frontend/config/NeoConfigureFile.kt @@ -9,18 +9,18 @@ import java.nio.file.Files * @author kiva */ open class NeoConfigureFile(val configureFile: File) { - private val configParser = NeoLangParser() - open protected var configVisitor : ConfigVisitor? = null + private val configParser = NeoLangParser() + open protected var configVisitor: ConfigVisitor? = null - fun getVisitor() = configVisitor ?: throw IllegalStateException("Configure file not loaded or parse failed.") + fun getVisitor() = configVisitor ?: throw IllegalStateException("Configure file not loaded or parse failed.") - open fun parseConfigure() = kotlin.runCatching { - val programCode = String(Files.readAllBytes(configureFile.toPath())) - configParser.setInputSource(programCode) + open fun parseConfigure() = kotlin.runCatching { + val programCode = String(Files.readAllBytes(configureFile.toPath())) + configParser.setInputSource(programCode) - val ast = configParser.parse() - val astVisitor = ast.visit().getVisitor(ConfigVisitor::class.java) ?: return false - astVisitor.start() - configVisitor = astVisitor.getCallback() - }.isSuccess + val ast = configParser.parse() + val astVisitor = ast.visit().getVisitor(ConfigVisitor::class.java) ?: return false + astVisitor.start() + configVisitor = astVisitor.getCallback() + }.isSuccess } diff --git a/app/src/main/java/io/neoterm/frontend/config/NeoPermission.kt b/app/src/main/java/io/neoterm/frontend/config/NeoPermission.kt index 428e3a9..d8c7f46 100644 --- a/app/src/main/java/io/neoterm/frontend/config/NeoPermission.kt +++ b/app/src/main/java/io/neoterm/frontend/config/NeoPermission.kt @@ -1,12 +1,12 @@ package io.neoterm.frontend.config import android.Manifest -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.app.AlertDialog import android.content.ActivityNotFoundException import android.content.DialogInterface import android.content.pm.PackageManager import android.os.Build +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat @@ -14,38 +14,46 @@ import androidx.core.content.ContextCompat * @author kiva */ object NeoPermission { - const val REQUEST_APP_PERMISSION = 10086 + const val REQUEST_APP_PERMISSION = 10086 - fun initAppPermission(context: AppCompatActivity, requestCode: Int) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return - } - - if (ContextCompat.checkSelfPermission(context, - Manifest.permission.READ_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - - if (ActivityCompat.shouldShowRequestPermissionRationale(context, - Manifest.permission.READ_EXTERNAL_STORAGE)) { - AlertDialog.Builder(context).setMessage("需要存储权限来访问存储设备上的文件") - .setPositiveButton(android.R.string.ok, { _: DialogInterface, _: Int -> - doRequestPermission(context, requestCode) - }) - .show() - - } else { - doRequestPermission(context, requestCode) - } - } + fun initAppPermission(context: AppCompatActivity, requestCode: Int) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return } - private fun doRequestPermission(context: AppCompatActivity, requestCode: Int) { - try { - ActivityCompat.requestPermissions(context, - arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), - requestCode) - } catch (ignore: ActivityNotFoundException) { - // for MIUI, we ignore it. - } + if (ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + != PackageManager.PERMISSION_GRANTED + ) { + + if (ActivityCompat.shouldShowRequestPermissionRationale( + context, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + ) { + AlertDialog.Builder(context).setMessage("需要存储权限来访问存储设备上的文件") + .setPositiveButton(android.R.string.ok, { _: DialogInterface, _: Int -> + doRequestPermission(context, requestCode) + }) + .show() + + } else { + doRequestPermission(context, requestCode) + } } + } + + private fun doRequestPermission(context: AppCompatActivity, requestCode: Int) { + try { + ActivityCompat.requestPermissions( + context, + arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), + requestCode + ) + } catch (ignore: ActivityNotFoundException) { + // for MIUI, we ignore it. + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/config/NeoPreference.kt b/app/src/main/java/io/neoterm/frontend/config/NeoPreference.kt index da2e104..dbf7437 100644 --- a/app/src/main/java/io/neoterm/frontend/config/NeoPreference.kt +++ b/app/src/main/java/io/neoterm/frontend/config/NeoPreference.kt @@ -20,252 +20,252 @@ import java.nio.file.Files */ object NeoPreference { - const val KEY_HAPPY_EGG = "neoterm_fun_happy" - const val KEY_FONT_SIZE = "neoterm_general_font_size" - const val KEY_CURRENT_SESSION = "neoterm_service_current_session" - const val KEY_SYSTEM_SHELL = "neoterm_core_system_shell" - const val KEY_SOURCES = "neoterm_package_enabled_sources" + const val KEY_HAPPY_EGG = "neoterm_fun_happy" + const val KEY_FONT_SIZE = "neoterm_general_font_size" + const val KEY_CURRENT_SESSION = "neoterm_service_current_session" + const val KEY_SYSTEM_SHELL = "neoterm_core_system_shell" + const val KEY_SOURCES = "neoterm_package_enabled_sources" - const val VALUE_HAPPY_EGG_TRIGGER = 8 + const val VALUE_HAPPY_EGG_TRIGGER = 8 - var MIN_FONT_SIZE: Int = 0 - private set - var MAX_FONT_SIZE: Int = 0 - private set + var MIN_FONT_SIZE: Int = 0 + private set + var MAX_FONT_SIZE: Int = 0 + private set - private var preference: SharedPreferences? = null + private var preference: SharedPreferences? = null - fun init(context: Context) { - preference = PreferenceManager.getDefaultSharedPreferences(context) + fun init(context: Context) { + preference = PreferenceManager.getDefaultSharedPreferences(context) - // This is a bit arbitrary and sub-optimal. We want to give a sensible default for minimum font size - // to prevent invisible text due to zoom be mistake: - val dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, context.resources.displayMetrics) - MIN_FONT_SIZE = (4f * dipInPixels).toInt() - MAX_FONT_SIZE = 256 + // This is a bit arbitrary and sub-optimal. We want to give a sensible default for minimum font size + // to prevent invisible text due to zoom be mistake: + val dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, context.resources.displayMetrics) + MIN_FONT_SIZE = (4f * dipInPixels).toInt() + MAX_FONT_SIZE = 256 - // load apt source - val sourceFile = File(NeoTermPath.SOURCE_FILE) - kotlin.runCatching { - Files.readAllBytes(sourceFile.toPath())?.let { - val source = String(it).trim().trimEnd() - val array = source.split(" ") - if (array.size >= 2 && array[0] == "deb") { - store(R.string.key_package_source, array[1]) - } - } + // load apt source + val sourceFile = File(NeoTermPath.SOURCE_FILE) + kotlin.runCatching { + Files.readAllBytes(sourceFile.toPath())?.let { + val source = String(it).trim().trimEnd() + val array = source.split(" ") + if (array.size >= 2 && array[0] == "deb") { + store(R.string.key_package_source, array[1]) } + } + } + } + + fun store(key: Int, value: Any) { + store(App.get().getString(key), value) + } + + fun store(key: String, value: Any) { + when (value) { + is Int -> preference!!.edit().putInt(key, value).apply() + is String -> preference!!.edit().putString(key, value).apply() + is Boolean -> preference!!.edit().putBoolean(key, value).apply() + } + } + + fun loadInt(key: Int, defaultValue: Int): Int { + return loadInt(App.get().getString(key), defaultValue) + } + + fun loadString(key: Int, defaultValue: String?): String { + return loadString(App.get().getString(key), defaultValue) + } + + fun loadBoolean(key: Int, defaultValue: Boolean): Boolean { + return loadBoolean(App.get().getString(key), defaultValue) + } + + fun loadInt(key: String?, defaultValue: Int): Int { + return preference!!.getInt(key, defaultValue) + } + + fun loadString(key: String?, defaultValue: String?): String { + return preference!!.getString(key, defaultValue) + } + + fun loadBoolean(key: String?, defaultValue: Boolean): Boolean { + return preference!!.getBoolean(key, defaultValue) + } + + fun storeCurrentSession(session: TerminalSession) { + preference!!.edit() + .putString(KEY_CURRENT_SESSION, session.mHandle) + .apply() + } + + fun getCurrentSession(termService: NeoTermService?): TerminalSession? { + val sessionHandle = PreferenceManager.getDefaultSharedPreferences(termService!!) + .getString(KEY_CURRENT_SESSION, "") + + return termService.sessions + .singleOrNull { it.mHandle == sessionHandle } + } + + fun setLoginShellName(loginProgramName: String?): Boolean { + if (loginProgramName == null) { + return false } - fun store(key: Int, value: Any) { - store(App.get().getString(key), value) + val loginProgramPath = findLoginProgram(loginProgramName) ?: return false + + store(R.string.key_general_shell, loginProgramName) + symlinkLoginShell(loginProgramPath) + return true + } + + fun getLoginShellName(): String { + return loadString(R.string.key_general_shell, DefaultValues.loginShell) + } + + fun getLoginShellPath(): String { + val loginProgramName = getLoginShellName() + + // Some programs like ssh needs it + val shell = File(NeoTermPath.NEOTERM_LOGIN_SHELL_PATH) + val loginProgramPath = findLoginProgram(loginProgramName) ?: { + setLoginShellName(DefaultValues.loginShell) + "${NeoTermPath.USR_PATH}/bin/${DefaultValues.loginShell}" + }() + + if (!shell.exists()) { + symlinkLoginShell(loginProgramPath) } - fun store(key: String, value: Any) { - when (value) { - is Int -> preference!!.edit().putInt(key, value).apply() - is String -> preference!!.edit().putString(key, value).apply() - is Boolean -> preference!!.edit().putBoolean(key, value).apply() - } + return loginProgramPath + } + + fun validateFontSize(fontSize: Int): Int { + return Math.max(MIN_FONT_SIZE, Math.min(fontSize, MAX_FONT_SIZE)) + } + + private fun symlinkLoginShell(loginProgramPath: String) { + File(NeoTermPath.CUSTOM_PATH).mkdirs() + try { + val shellSymlink = File(NeoTermPath.NEOTERM_LOGIN_SHELL_PATH) + if (shellSymlink.exists()) { + shellSymlink.delete() + } + Os.symlink(loginProgramPath, NeoTermPath.NEOTERM_LOGIN_SHELL_PATH) + Os.chmod(NeoTermPath.NEOTERM_LOGIN_SHELL_PATH, 448 /* Decimal of 0700 */) + } catch (e: ErrnoException) { + NLog.e("Preference", "Failed to symlink login shell: ${e.localizedMessage}") + e.printStackTrace() } + } - fun loadInt(key: Int, defaultValue: Int): Int { - return loadInt(App.get().getString(key), defaultValue) - } + fun findLoginProgram(loginProgramName: String): String? { + val file = File("${NeoTermPath.USR_PATH}/bin", loginProgramName) + return if (file.canExecute()) file.absolutePath else null + } - fun loadString(key: Int, defaultValue: String?): String { - return loadString(App.get().getString(key), defaultValue) - } + fun getFontSize(): Int { + return loadInt( + KEY_FONT_SIZE, + DefaultValues.fontSize + ) + } - fun loadBoolean(key: Int, defaultValue: Boolean): Boolean { - return loadBoolean(App.get().getString(key), defaultValue) - } + fun getInitialCommand(): String { + return loadString( + R.string.key_general_initial_command, + DefaultValues.initialCommand + ) + } - fun loadInt(key: String?, defaultValue: Int): Int { - return preference!!.getInt(key, defaultValue) - } + fun isBellEnabled(): Boolean { + return loadBoolean( + R.string.key_general_bell, + DefaultValues.enableBell + ) + } - fun loadString(key: String?, defaultValue: String?): String { - return preference!!.getString(key, defaultValue) - } + fun isVibrateEnabled(): Boolean { + return loadBoolean( + R.string.key_general_vibrate, + DefaultValues.enableVibrate + ) + } - fun loadBoolean(key: String?, defaultValue: Boolean): Boolean { - return preference!!.getBoolean(key, defaultValue) - } + fun isExecveWrapperEnabled(): Boolean { + return loadBoolean( + R.string.key_general_use_execve_wrapper, + DefaultValues.enableExecveWrapper + ) + } - fun storeCurrentSession(session: TerminalSession) { - preference!!.edit() - .putString(KEY_CURRENT_SESSION, session.mHandle) - .apply() - } + fun isSpecialVolumeKeysEnabled(): Boolean { + return loadBoolean( + R.string.key_general_volume_as_control, + DefaultValues.enableSpecialVolumeKeys + ) + } - fun getCurrentSession(termService: NeoTermService?): TerminalSession? { - val sessionHandle = PreferenceManager.getDefaultSharedPreferences(termService!!) - .getString(KEY_CURRENT_SESSION, "") + fun isAutoCompletionEnabled(): Boolean { + return loadBoolean( + R.string.key_general_auto_completion, + DefaultValues.enableAutoCompletion + ) + } - return termService.sessions - .singleOrNull { it.mHandle == sessionHandle } - } + fun isBackButtonBeMappedToEscapeEnabled(): Boolean { + return loadBoolean( + R.string.key_generaL_backspace_map_to_esc, + DefaultValues.enableBackButtonBeMappedToEscape + ) + } - fun setLoginShellName(loginProgramName: String?): Boolean { - if (loginProgramName == null) { - return false - } + fun isExtraKeysEnabled(): Boolean { + return loadBoolean( + R.string.key_ui_eks_enabled, + DefaultValues.enableExtraKeys + ) + } - val loginProgramPath = findLoginProgram(loginProgramName) ?: return false + fun isExplicitExtraKeysWeightEnabled(): Boolean { + return loadBoolean( + R.string.key_ui_eks_weight_explicit, + DefaultValues.enableExplicitExtraKeysWeight + ) + } - store(R.string.key_general_shell, loginProgramName) - symlinkLoginShell(loginProgramPath) - return true - } + fun isFullScreenEnabled(): Boolean { + return loadBoolean( + R.string.key_ui_fullscreen, + DefaultValues.enableFullScreen + ) + } - fun getLoginShellName(): String { - return loadString(R.string.key_general_shell, DefaultValues.loginShell) - } + fun isHideToolbarEnabled(): Boolean { + return loadBoolean( + R.string.key_ui_hide_toolbar, + DefaultValues.enableAutoHideToolbar + ) + } - fun getLoginShellPath(): String { - val loginProgramName = getLoginShellName() + fun isNextTabEnabled(): Boolean { + return loadBoolean( + R.string.key_ui_next_tab_anim, + DefaultValues.enableSwitchNextTab + ) + } - // Some programs like ssh needs it - val shell = File(NeoTermPath.NEOTERM_LOGIN_SHELL_PATH) - val loginProgramPath = findLoginProgram(loginProgramName) ?: { - setLoginShellName(DefaultValues.loginShell) - "${NeoTermPath.USR_PATH}/bin/${DefaultValues.loginShell}" - }() + fun isWordBasedImeEnabled(): Boolean { + return loadBoolean( + R.string.key_general_enable_word_based_ime, + DefaultValues.enableWordBasedIme + ) + } - if (!shell.exists()) { - symlinkLoginShell(loginProgramPath) - } - - return loginProgramPath - } - - fun validateFontSize(fontSize: Int): Int { - return Math.max(MIN_FONT_SIZE, Math.min(fontSize, MAX_FONT_SIZE)) - } - - private fun symlinkLoginShell(loginProgramPath: String) { - File(NeoTermPath.CUSTOM_PATH).mkdirs() - try { - val shellSymlink = File(NeoTermPath.NEOTERM_LOGIN_SHELL_PATH) - if (shellSymlink.exists()) { - shellSymlink.delete() - } - Os.symlink(loginProgramPath, NeoTermPath.NEOTERM_LOGIN_SHELL_PATH) - Os.chmod(NeoTermPath.NEOTERM_LOGIN_SHELL_PATH, 448 /* Decimal of 0700 */) - } catch (e: ErrnoException) { - NLog.e("Preference", "Failed to symlink login shell: ${e.localizedMessage}") - e.printStackTrace() - } - } - - fun findLoginProgram(loginProgramName: String): String? { - val file = File("${NeoTermPath.USR_PATH}/bin", loginProgramName) - return if (file.canExecute()) file.absolutePath else null - } - - fun getFontSize(): Int { - return loadInt( - KEY_FONT_SIZE, - DefaultValues.fontSize - ) - } - - fun getInitialCommand(): String { - return loadString( - R.string.key_general_initial_command, - DefaultValues.initialCommand - ) - } - - fun isBellEnabled(): Boolean { - return loadBoolean( - R.string.key_general_bell, - DefaultValues.enableBell - ) - } - - fun isVibrateEnabled(): Boolean { - return loadBoolean( - R.string.key_general_vibrate, - DefaultValues.enableVibrate - ) - } - - fun isExecveWrapperEnabled(): Boolean { - return loadBoolean( - R.string.key_general_use_execve_wrapper, - DefaultValues.enableExecveWrapper - ) - } - - fun isSpecialVolumeKeysEnabled(): Boolean { - return loadBoolean( - R.string.key_general_volume_as_control, - DefaultValues.enableSpecialVolumeKeys - ) - } - - fun isAutoCompletionEnabled(): Boolean { - return loadBoolean( - R.string.key_general_auto_completion, - DefaultValues.enableAutoCompletion - ) - } - - fun isBackButtonBeMappedToEscapeEnabled(): Boolean { - return loadBoolean( - R.string.key_generaL_backspace_map_to_esc, - DefaultValues.enableBackButtonBeMappedToEscape - ) - } - - fun isExtraKeysEnabled(): Boolean { - return loadBoolean( - R.string.key_ui_eks_enabled, - DefaultValues.enableExtraKeys - ) - } - - fun isExplicitExtraKeysWeightEnabled(): Boolean { - return loadBoolean( - R.string.key_ui_eks_weight_explicit, - DefaultValues.enableExplicitExtraKeysWeight - ) - } - - fun isFullScreenEnabled(): Boolean { - return loadBoolean( - R.string.key_ui_fullscreen, - DefaultValues.enableFullScreen - ) - } - - fun isHideToolbarEnabled(): Boolean { - return loadBoolean( - R.string.key_ui_hide_toolbar, - DefaultValues.enableAutoHideToolbar - ) - } - - fun isNextTabEnabled(): Boolean { - return loadBoolean( - R.string.key_ui_next_tab_anim, - DefaultValues.enableSwitchNextTab - ) - } - - fun isWordBasedImeEnabled(): Boolean { - return loadBoolean( - R.string.key_general_enable_word_based_ime, - DefaultValues.enableWordBasedIme - ) - } - - /** - * TODO - * To print the job name about to be executed in bash: - * $ trap 'echo -ne "\e]0;${BASH_COMMAND%% *}\x07"' DEBUG - * $ PS1='$(echo -ne "\e]0;$PWD\x07")\$ ' - */ + /** + * TODO + * To print the job name about to be executed in bash: + * $ trap 'echo -ne "\e]0;${BASH_COMMAND%% *}\x07"' DEBUG + * $ PS1='$(echo -ne "\e]0;$PWD\x07")\$ ' + */ } diff --git a/app/src/main/java/io/neoterm/frontend/config/NeoTermPath.kt b/app/src/main/java/io/neoterm/frontend/config/NeoTermPath.kt index d537e15..ad211d4 100644 --- a/app/src/main/java/io/neoterm/frontend/config/NeoTermPath.kt +++ b/app/src/main/java/io/neoterm/frontend/config/NeoTermPath.kt @@ -6,30 +6,30 @@ import android.annotation.SuppressLint * @author kiva */ object NeoTermPath { - @SuppressLint("SdCardPath") - const val ROOT_PATH = "/data/data/io.neoterm/files" - const val USR_PATH = "$ROOT_PATH/usr" - const val HOME_PATH = "$ROOT_PATH/home" - const val APT_BIN_PATH = "$USR_PATH/bin/apt" - const val LIB_PATH = "$USR_PATH/lib" + @SuppressLint("SdCardPath") + const val ROOT_PATH = "/data/data/io.neoterm/files" + const val USR_PATH = "$ROOT_PATH/usr" + const val HOME_PATH = "$ROOT_PATH/home" + const val APT_BIN_PATH = "$USR_PATH/bin/apt" + const val LIB_PATH = "$USR_PATH/lib" - const val CUSTOM_PATH = "$HOME_PATH/.neoterm" - const val NEOTERM_LOGIN_SHELL_PATH = "$CUSTOM_PATH/shell" - const val EKS_PATH = "$CUSTOM_PATH/eks" - const val EKS_DEFAULT_FILE = "$EKS_PATH/default.nl" - const val FONT_PATH = "$CUSTOM_PATH/font" - const val COLORS_PATH = "$CUSTOM_PATH/color" - const val USER_SCRIPT_PATH = "$CUSTOM_PATH/script" - const val PROFILE_PATH = "$CUSTOM_PATH/profile" + const val CUSTOM_PATH = "$HOME_PATH/.neoterm" + const val NEOTERM_LOGIN_SHELL_PATH = "$CUSTOM_PATH/shell" + const val EKS_PATH = "$CUSTOM_PATH/eks" + const val EKS_DEFAULT_FILE = "$EKS_PATH/default.nl" + const val FONT_PATH = "$CUSTOM_PATH/font" + const val COLORS_PATH = "$CUSTOM_PATH/color" + const val USER_SCRIPT_PATH = "$CUSTOM_PATH/script" + const val PROFILE_PATH = "$CUSTOM_PATH/profile" - const val SOURCE_FILE = "$USR_PATH/etc/apt/sources.list" - const val PACKAGE_LIST_DIR = "$USR_PATH/var/lib/apt/lists" + const val SOURCE_FILE = "$USR_PATH/etc/apt/sources.list" + const val PACKAGE_LIST_DIR = "$USR_PATH/var/lib/apt/lists" - private const val SOURCE = "https://raw.githubusercontent.com/NeoTerm/NeoTerm-repo/main" + private const val SOURCE = "https://raw.githubusercontent.com/NeoTerm/NeoTerm-repo/main" - val DEFAULT_MAIN_PACKAGE_SOURCE: String + val DEFAULT_MAIN_PACKAGE_SOURCE: String - init { - DEFAULT_MAIN_PACKAGE_SOURCE = SOURCE - } + init { + DEFAULT_MAIN_PACKAGE_SOURCE = SOURCE + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/floating/TerminalDialog.kt b/app/src/main/java/io/neoterm/frontend/floating/TerminalDialog.kt index 6e50717..9833bc5 100644 --- a/app/src/main/java/io/neoterm/frontend/floating/TerminalDialog.kt +++ b/app/src/main/java/io/neoterm/frontend/floating/TerminalDialog.kt @@ -1,8 +1,8 @@ package io.neoterm.frontend.floating -import androidx.appcompat.app.AlertDialog import android.content.Context import android.content.DialogInterface +import androidx.appcompat.app.AlertDialog import io.neoterm.R import io.neoterm.backend.TerminalSession import io.neoterm.frontend.session.shell.ShellParameter @@ -16,84 +16,84 @@ import io.neoterm.utils.TerminalUtils */ class TerminalDialog(val context: Context) { - interface SessionFinishedCallback { - fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?) + interface SessionFinishedCallback { + fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?) + } + + private var termWindowView = WindowTermView(context) + private var terminalSessionCallback: BasicSessionCallback + private var dialog: AlertDialog? = null + private var terminalSession: TerminalSession? = null + private var sessionFinishedCallback: SessionFinishedCallback? = null + private var cancelListener: DialogInterface.OnCancelListener? = null + + init { + termWindowView.setTerminalViewClient(BasicViewClient(termWindowView.terminalView)) + + terminalSessionCallback = object : BasicSessionCallback(termWindowView.terminalView) { + override fun onSessionFinished(finishedSession: TerminalSession?) { + sessionFinishedCallback?.onSessionFinished(this@TerminalDialog, finishedSession) + super.onSessionFinished(finishedSession) + } + } + } + + fun execute(executablePath: String, arguments: Array?): TerminalDialog { + if (terminalSession != null) { + terminalSession?.finishIfRunning() } - private var termWindowView = WindowTermView(context) - private var terminalSessionCallback: BasicSessionCallback - private var dialog: AlertDialog? = null - private var terminalSession: TerminalSession? = null - private var sessionFinishedCallback: SessionFinishedCallback? = null - private var cancelListener: DialogInterface.OnCancelListener? = null + dialog = AlertDialog.Builder(context) + .setView(termWindowView.rootView) + .setOnCancelListener { + terminalSession?.finishIfRunning() + cancelListener?.onCancel(it) + } + .create() - init { - termWindowView.setTerminalViewClient(BasicViewClient(termWindowView.terminalView)) - - terminalSessionCallback = object : BasicSessionCallback(termWindowView.terminalView) { - override fun onSessionFinished(finishedSession: TerminalSession?) { - sessionFinishedCallback?.onSessionFinished(this@TerminalDialog, finishedSession) - super.onSessionFinished(finishedSession) - } - } + val parameter = ShellParameter() + .executablePath(executablePath) + .arguments(arguments) + .callback(terminalSessionCallback) + .systemShell(false) + terminalSession = TerminalUtils.createSession(context, parameter) + if (terminalSession is ShellTermSession) { + (terminalSession as ShellTermSession).exitPrompt = context.getString(R.string.process_exit_prompt_press_back) } + termWindowView.attachSession(terminalSession) + return this + } - fun execute(executablePath: String, arguments: Array?): TerminalDialog { - if (terminalSession != null) { - terminalSession?.finishIfRunning() - } + fun onDismiss(cancelListener: DialogInterface.OnCancelListener?): TerminalDialog { + this.cancelListener = cancelListener + return this + } - dialog = AlertDialog.Builder(context) - .setView(termWindowView.rootView) - .setOnCancelListener { - terminalSession?.finishIfRunning() - cancelListener?.onCancel(it) - } - .create() + fun setTitle(title: String?): TerminalDialog { + dialog?.setTitle(title) + return this + } - val parameter = ShellParameter() - .executablePath(executablePath) - .arguments(arguments) - .callback(terminalSessionCallback) - .systemShell(false) - terminalSession = TerminalUtils.createSession(context, parameter) - if (terminalSession is ShellTermSession) { - (terminalSession as ShellTermSession).exitPrompt = context.getString(R.string.process_exit_prompt_press_back) - } - termWindowView.attachSession(terminalSession) - return this - } - - fun onDismiss(cancelListener: DialogInterface.OnCancelListener?): TerminalDialog { - this.cancelListener = cancelListener - return this - } - - fun setTitle(title: String?): TerminalDialog { - dialog?.setTitle(title) - return this - } - - fun onFinish(finishedCallback: SessionFinishedCallback): TerminalDialog { - this.sessionFinishedCallback = finishedCallback - return this - } - - fun show(title: String?) { - dialog?.setTitle(title) - dialog?.setCanceledOnTouchOutside(false) - dialog?.show() - } - - fun dismiss(): TerminalDialog { - dialog?.dismiss() - return this - } - - fun imeEnabled(enabled: Boolean): TerminalDialog { - if (enabled) { - termWindowView.setInputMethodEnabled(true) - } - return this + fun onFinish(finishedCallback: SessionFinishedCallback): TerminalDialog { + this.sessionFinishedCallback = finishedCallback + return this + } + + fun show(title: String?) { + dialog?.setTitle(title) + dialog?.setCanceledOnTouchOutside(false) + dialog?.show() + } + + fun dismiss(): TerminalDialog { + dialog?.dismiss() + return this + } + + fun imeEnabled(enabled: Boolean): TerminalDialog { + if (enabled) { + termWindowView.setInputMethodEnabled(true) } + return this + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/floating/WindowTermView.kt b/app/src/main/java/io/neoterm/frontend/floating/WindowTermView.kt index 20ed7fa..c07b22e 100644 --- a/app/src/main/java/io/neoterm/frontend/floating/WindowTermView.kt +++ b/app/src/main/java/io/neoterm/frontend/floating/WindowTermView.kt @@ -14,26 +14,26 @@ import io.neoterm.utils.TerminalUtils * @author kiva */ class WindowTermView(val context: Context) { - @SuppressLint("InflateParams") - var rootView: View = LayoutInflater.from(context).inflate(R.layout.ui_term_dialog, null, false) - private set - var terminalView: TerminalView = rootView.findViewById(R.id.terminal_view_dialog) - private set + @SuppressLint("InflateParams") + var rootView: View = LayoutInflater.from(context).inflate(R.layout.ui_term_dialog, null, false) + private set + var terminalView: TerminalView = rootView.findViewById(R.id.terminal_view_dialog) + private set - init { - TerminalUtils.setupTerminalView(terminalView) - } + init { + TerminalUtils.setupTerminalView(terminalView) + } - fun setTerminalViewClient(terminalViewClient: TerminalViewClient?) { - terminalView.setTerminalViewClient(terminalViewClient) - } + fun setTerminalViewClient(terminalViewClient: TerminalViewClient?) { + terminalView.setTerminalViewClient(terminalViewClient) + } - fun attachSession(terminalSession: TerminalSession?) { - terminalView.attachSession(terminalSession) - } + fun attachSession(terminalSession: TerminalSession?) { + terminalView.attachSession(terminalSession) + } - fun setInputMethodEnabled(enabled: Boolean) { - terminalView.isFocusable = enabled - terminalView.isFocusableInTouchMode = enabled - } + fun setInputMethodEnabled(enabled: Boolean) { + terminalView.isFocusable = enabled + terminalView.isFocusableInTouchMode = enabled + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/logging/NLog.kt b/app/src/main/java/io/neoterm/frontend/logging/NLog.kt index 0278569..13c8d18 100644 --- a/app/src/main/java/io/neoterm/frontend/logging/NLog.kt +++ b/app/src/main/java/io/neoterm/frontend/logging/NLog.kt @@ -1,8 +1,8 @@ package io.neoterm.frontend.logging import android.content.Context -import androidx.annotation.IntDef import android.util.Log +import androidx.annotation.IntDef import java.io.* import java.text.SimpleDateFormat import java.util.* @@ -14,303 +14,312 @@ import java.util.zip.Inflater object NLog { - const val V = Log.VERBOSE - const val D = Log.DEBUG - const val I = Log.INFO - const val W = Log.WARN - const val E = Log.ERROR - const val A = Log.ASSERT + const val V = Log.VERBOSE + const val D = Log.DEBUG + const val I = Log.INFO + const val W = Log.WARN + const val E = Log.ERROR + const val A = Log.ASSERT - @IntDef(V.toLong().toInt(), D.toLong().toInt(), I.toLong().toInt(), W.toLong().toInt(), E.toLong().toInt(), A.toLong().toInt()) - @Retention(AnnotationRetention.SOURCE) - private annotation class TYPE + @IntDef( + V.toLong().toInt(), + D.toLong().toInt(), + I.toLong().toInt(), + W.toLong().toInt(), + E.toLong().toInt(), + A.toLong().toInt() + ) + @Retention(AnnotationRetention.SOURCE) + private annotation class TYPE - private val T = charArrayOf('V', 'D', 'I', 'W', 'E', 'A') - private val FILE = 0x10 + private val T = charArrayOf('V', 'D', 'I', 'W', 'E', 'A') + private val FILE = 0x10 - private var executor: ExecutorService? = null - private var logDir: String? = null // log存储目录 + private var executor: ExecutorService? = null + private var logDir: String? = null // log存储目录 - private var sLogSwitch = true // log总开关,默认开 - private var sLog2ConsoleSwitch = true // logcat是否打印,默认打印 - private var sGlobalTag: String = "" // log标签 - private var sTagIsSpace = true // log标签是否为空白 - private var sLogHeadSwitch = true // log头部开关,默认开 - private var sLog2FileSwitch = false// log写入文件开关,默认关 - private var sConsoleFilter = V // log控制台过滤器 - private var sFileFilter = V // log文件过滤器 + private var sLogSwitch = true // log总开关,默认开 + private var sLog2ConsoleSwitch = true // logcat是否打印,默认打印 + private var sGlobalTag: String = "" // log标签 + private var sTagIsSpace = true // log标签是否为空白 + private var sLogHeadSwitch = true // log头部开关,默认开 + private var sLog2FileSwitch = false// log写入文件开关,默认关 + private var sConsoleFilter = V // log控制台过滤器 + private var sFileFilter = V // log文件过滤器 - private val LINE_SEP = System.getProperty("line.separator") - private val MAX_LEN = 4000 - private val FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS ", Locale.getDefault()) + private val LINE_SEP = System.getProperty("line.separator") + private val MAX_LEN = 4000 + private val FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS ", Locale.getDefault()) - private val NULL_TIPS = "Log with null object." - private val ARGS = "args" + private val NULL_TIPS = "Log with null object." + private val ARGS = "args" - fun init(context: Context) { - logDir = context.getDir("logs", Context.MODE_PRIVATE).absolutePath - sGlobalTag = "NeoTerm" + fun init(context: Context) { + logDir = context.getDir("logs", Context.MODE_PRIVATE).absolutePath + sGlobalTag = "NeoTerm" + } + + fun v(contents: Any) { + log(V, sGlobalTag, contents) + } + + fun v(tag: String, vararg contents: Any) { + log(V, tag, *contents) + } + + fun d(contents: Any) { + log(D, sGlobalTag, contents) + } + + fun d(tag: String, vararg contents: Any) { + log(D, tag, *contents) + } + + fun i(contents: Any) { + log(I, sGlobalTag, contents) + } + + fun i(tag: String, vararg contents: Any) { + log(I, tag, *contents) + } + + fun w(contents: Any) { + log(W, sGlobalTag, contents) + } + + fun w(tag: String, vararg contents: Any) { + log(W, tag, *contents) + } + + fun e(contents: Any) { + log(E, sGlobalTag, contents) + } + + fun e(tag: String, vararg contents: Any) { + log(E, tag, *contents) + } + + fun a(contents: Any) { + log(A, sGlobalTag, contents) + } + + fun a(tag: String, vararg contents: Any) { + log(A, tag, *contents) + } + + fun file(contents: Any) { + log(FILE or D, sGlobalTag, contents) + } + + fun file(@TYPE type: Int, contents: Any) { + log(FILE or type, sGlobalTag, contents) + } + + fun file(tag: String, contents: Any) { + log(FILE or D, tag, contents) + } + + fun file(@TYPE type: Int, tag: String, contents: Any) { + log(FILE or type, tag, contents) + } + + private fun log(type: Int, tag: String, vararg contents: Any) { + if (!sLogSwitch || !sLog2ConsoleSwitch && !sLog2FileSwitch) return + val type_low = type and 0x0f + val type_high = type and 0xf0 + if (type_low < sConsoleFilter && type_low < sFileFilter) return + val tagAndHead = processTagAndHead(tag) + val body = processBody(*contents) + if (sLog2ConsoleSwitch && type_low >= sConsoleFilter) { + printToConsole(type_low, tagAndHead[0], tagAndHead[1] + body) } - - fun v(contents: Any) { - log(V, sGlobalTag, contents) + if (sLog2FileSwitch || type_high == FILE) { + if (type_low >= sFileFilter) printToFile(type_low, tagAndHead[0], tagAndHead[2] + body) } + } - fun v(tag: String, vararg contents: Any) { - log(V, tag, *contents) + private fun processTagAndHead(tag: String): Array { + var returnTag = tag + if (!sTagIsSpace && !sLogHeadSwitch) { + returnTag = sGlobalTag + } else { + returnTag = "$sGlobalTag-$returnTag" + // DO NOT USE Thread.currentThread + val targetElement = Throwable().stackTrace[3] + var className = targetElement.className + val classNameInfo = className.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (classNameInfo.isNotEmpty()) { + className = classNameInfo[classNameInfo.size - 1] + } + if (className.contains("$")) { + className = className.split("\\$".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] + } + if (sTagIsSpace) { + returnTag = if (isSpace(returnTag)) className else returnTag + } + if (sLogHeadSwitch) { + val head = Formatter() + .format( + "[Thread:%s], %s(%s:%d): ", + Thread.currentThread().name, + targetElement.methodName, + targetElement.fileName, + targetElement.lineNumber + ) + .toString() + return arrayOf(returnTag, head, head) + } } + return arrayOf(returnTag, "", ": ") + } - fun d(contents: Any) { - log(D, sGlobalTag, contents) - } - - fun d(tag: String, vararg contents: Any) { - log(D, tag, *contents) - } - - fun i(contents: Any) { - log(I, sGlobalTag, contents) - } - - fun i(tag: String, vararg contents: Any) { - log(I, tag, *contents) - } - - fun w(contents: Any) { - log(W, sGlobalTag, contents) - } - - fun w(tag: String, vararg contents: Any) { - log(W, tag, *contents) - } - - fun e(contents: Any) { - log(E, sGlobalTag, contents) - } - - fun e(tag: String, vararg contents: Any) { - log(E, tag, *contents) - } - - fun a(contents: Any) { - log(A, sGlobalTag, contents) - } - - fun a(tag: String, vararg contents: Any) { - log(A, tag, *contents) - } - - fun file(contents: Any) { - log(FILE or D, sGlobalTag, contents) - } - - fun file(@TYPE type: Int, contents: Any) { - log(FILE or type, sGlobalTag, contents) - } - - fun file(tag: String, contents: Any) { - log(FILE or D, tag, contents) - } - - fun file(@TYPE type: Int, tag: String, contents: Any) { - log(FILE or type, tag, contents) - } - - private fun log(type: Int, tag: String, vararg contents: Any) { - if (!sLogSwitch || !sLog2ConsoleSwitch && !sLog2FileSwitch) return - val type_low = type and 0x0f - val type_high = type and 0xf0 - if (type_low < sConsoleFilter && type_low < sFileFilter) return - val tagAndHead = processTagAndHead(tag) - val body = processBody(*contents) - if (sLog2ConsoleSwitch && type_low >= sConsoleFilter) { - printToConsole(type_low, tagAndHead[0], tagAndHead[1] + body) - } - if (sLog2FileSwitch || type_high == FILE) { - if (type_low >= sFileFilter) printToFile(type_low, tagAndHead[0], tagAndHead[2] + body) + private fun processBody(vararg contents: Any): String { + var body = NULL_TIPS + if (contents.isNotEmpty()) { + if (contents.size == 1) { + body = contents[0].toString() + } else { + body = buildString { + var index = 0 + contents.forEach { + append(ARGS) + .append("[") + .append(index++) + .append("]") + .append(" = ") + .append(it.toString()) + .append(LINE_SEP) + } } + } } + return body + } - private fun processTagAndHead(tag: String): Array { - var returnTag = tag - if (!sTagIsSpace && !sLogHeadSwitch) { - returnTag = sGlobalTag - } else { - returnTag = "$sGlobalTag-$returnTag" - // DO NOT USE Thread.currentThread - val targetElement = Throwable().stackTrace[3] - var className = targetElement.className - val classNameInfo = className.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - if (classNameInfo.isNotEmpty()) { - className = classNameInfo[classNameInfo.size - 1] - } - if (className.contains("$")) { - className = className.split("\\$".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] - } - if (sTagIsSpace) { - returnTag = if (isSpace(returnTag)) className else returnTag - } - if (sLogHeadSwitch) { - val head = Formatter() - .format("[Thread:%s], %s(%s:%d): ", - Thread.currentThread().name, - targetElement.methodName, - targetElement.fileName, - targetElement.lineNumber) - .toString() - return arrayOf(returnTag, head, head) - } - } - return arrayOf(returnTag, "", ": ") + private fun printToConsole(type: Int, tag: String, msg: String) { + val len = msg.length + val countOfSub = len / MAX_LEN + if (countOfSub > 0) { + print(type, tag, msg.substring(0, MAX_LEN)) + var sub: String + var index = MAX_LEN + for (i in 1..countOfSub - 1) { + sub = msg.substring(index, index + MAX_LEN) + print(type, tag, sub) + index += MAX_LEN + } + sub = msg.substring(index, len) + print(type, tag, sub) + } else { + print(type, tag, msg) } + } - private fun processBody(vararg contents: Any): String { - var body = NULL_TIPS - if (contents.isNotEmpty()) { - if (contents.size == 1) { - body = contents[0].toString() - } else { - body = buildString { - var index = 0 - contents.forEach { - append(ARGS) - .append("[") - .append(index++) - .append("]") - .append(" = ") - .append(it.toString()) - .append(LINE_SEP) - } - } - } - } - return body + private fun print(type: Int, tag: String, msg: String) { + Log.println(type, tag, msg) + } + + private fun printToFile(type: Int, tag: String, msg: String) { + val now = Date(System.currentTimeMillis()) + val format = FORMAT.format(now) + val date = format.substring(0, 5) + val time = format.substring(6) + val fullPath = logDir + date + ".txt" + if (!createOrExistsFile(fullPath)) { + Log.e(tag, "log to $fullPath failed!") + return } - - private fun printToConsole(type: Int, tag: String, msg: String) { - val len = msg.length - val countOfSub = len / MAX_LEN - if (countOfSub > 0) { - print(type, tag, msg.substring(0, MAX_LEN)) - var sub: String - var index = MAX_LEN - for (i in 1..countOfSub - 1) { - sub = msg.substring(index, index + MAX_LEN) - print(type, tag, sub) - index += MAX_LEN - } - sub = msg.substring(index, len) - print(type, tag, sub) - } else { - print(type, tag, msg) - } + val sb = StringBuilder() + sb.append(time) + .append(T[type - V]) + .append("/") + .append(tag) + .append(msg) + .append(LINE_SEP) + val content = sb.toString() + if (executor == null) { + executor = Executors.newSingleThreadExecutor() } - - private fun print(type: Int, tag: String, msg: String) { - Log.println(type, tag, msg) - } - - private fun printToFile(type: Int, tag: String, msg: String) { - val now = Date(System.currentTimeMillis()) - val format = FORMAT.format(now) - val date = format.substring(0, 5) - val time = format.substring(6) - val fullPath = logDir + date + ".txt" - if (!createOrExistsFile(fullPath)) { - Log.e(tag, "log to $fullPath failed!") - return - } - val sb = StringBuilder() - sb.append(time) - .append(T[type - V]) - .append("/") - .append(tag) - .append(msg) - .append(LINE_SEP) - val content = sb.toString() - if (executor == null) { - executor = Executors.newSingleThreadExecutor() - } - executor!!.execute { - var bw: BufferedWriter? = null - try { - bw = BufferedWriter(FileWriter(fullPath, true)) - bw.write(content) - Log.d(tag, "log to $fullPath success!") - } catch (e: IOException) { - e.printStackTrace() - Log.e(tag, "log to $fullPath failed!") - } finally { - try { - if (bw != null) { - bw.close() - } - } catch (e: IOException) { - e.printStackTrace() - } - - } - } - } - - private fun createOrExistsFile(filePath: String): Boolean { - val file = File(filePath) - if (file.exists()) return file.isFile - if (!createOrExistsDir(file.parentFile)) return false + executor!!.execute { + var bw: BufferedWriter? = null + try { + bw = BufferedWriter(FileWriter(fullPath, true)) + bw.write(content) + Log.d(tag, "log to $fullPath success!") + } catch (e: IOException) { + e.printStackTrace() + Log.e(tag, "log to $fullPath failed!") + } finally { try { - return file.createNewFile() + if (bw != null) { + bw.close() + } } catch (e: IOException) { - e.printStackTrace() - return false + e.printStackTrace() } + } + } + } + + private fun createOrExistsFile(filePath: String): Boolean { + val file = File(filePath) + if (file.exists()) return file.isFile + if (!createOrExistsDir(file.parentFile)) return false + try { + return file.createNewFile() + } catch (e: IOException) { + e.printStackTrace() + return false } - private fun createOrExistsDir(file: File?): Boolean { - return file != null && if (file.exists()) file.isDirectory else file.mkdirs() - } + } - private fun isSpace(s: String?): Boolean { - return s?.isEmpty() ?: true - } + private fun createOrExistsDir(file: File?): Boolean { + return file != null && if (file.exists()) file.isDirectory else file.mkdirs() + } - fun compress(input: ByteArray): ByteArray { - val bos = ByteArrayOutputStream() - val compressor = Deflater(1) + private fun isSpace(s: String?): Boolean { + return s?.isEmpty() ?: true + } + + fun compress(input: ByteArray): ByteArray { + val bos = ByteArrayOutputStream() + val compressor = Deflater(1) + try { + compressor.setInput(input) + compressor.finish() + val buf = ByteArray(2048) + while (!compressor.finished()) { + val count = compressor.deflate(buf) + bos.write(buf, 0, count) + } + } finally { + compressor.end() + } + return bos.toByteArray() + } + + fun uncompress(input: ByteArray): ByteArray { + val bos = ByteArrayOutputStream() + val inflater = Inflater() + try { + inflater.setInput(input) + val buf = ByteArray(2048) + while (!inflater.finished()) { + var count = 0 try { - compressor.setInput(input) - compressor.finish() - val buf = ByteArray(2048) - while (!compressor.finished()) { - val count = compressor.deflate(buf) - bos.write(buf, 0, count) - } - } finally { - compressor.end() + count = inflater.inflate(buf) + } catch (e: DataFormatException) { + e.printStackTrace() } - return bos.toByteArray() - } - fun uncompress(input: ByteArray): ByteArray { - val bos = ByteArrayOutputStream() - val inflater = Inflater() - try { - inflater.setInput(input) - val buf = ByteArray(2048) - while (!inflater.finished()) { - var count = 0 - try { - count = inflater.inflate(buf) - } catch (e: DataFormatException) { - e.printStackTrace() - } - - bos.write(buf, 0, count) - } - } finally { - inflater.end() - } - return bos.toByteArray() + bos.write(buf, 0, count) + } + } finally { + inflater.end() } + return bos.toByteArray() + } } diff --git a/app/src/main/java/io/neoterm/frontend/session/shell/ShellParameter.kt b/app/src/main/java/io/neoterm/frontend/session/shell/ShellParameter.kt index 76cabef..ff7f37f 100644 --- a/app/src/main/java/io/neoterm/frontend/session/shell/ShellParameter.kt +++ b/app/src/main/java/io/neoterm/frontend/session/shell/ShellParameter.kt @@ -7,62 +7,62 @@ import io.neoterm.bridge.SessionId * @author kiva */ class ShellParameter { - var sessionId: SessionId? = null - var executablePath: String? = null - var arguments: Array? = null - var cwd: String? = null - var initialCommand: String? = null - var env: Array>? = null - var sessionCallback: TerminalSession.SessionChangedCallback? = null - var systemShell: Boolean = false - var shellProfile: ShellProfile? = null + var sessionId: SessionId? = null + var executablePath: String? = null + var arguments: Array? = null + var cwd: String? = null + var initialCommand: String? = null + var env: Array>? = null + var sessionCallback: TerminalSession.SessionChangedCallback? = null + var systemShell: Boolean = false + var shellProfile: ShellProfile? = null - fun executablePath(executablePath: String?): ShellParameter { - this.executablePath = executablePath - return this - } + fun executablePath(executablePath: String?): ShellParameter { + this.executablePath = executablePath + return this + } - fun arguments(arguments: Array?): ShellParameter { - this.arguments = arguments - return this - } + fun arguments(arguments: Array?): ShellParameter { + this.arguments = arguments + return this + } - fun currentWorkingDirectory(cwd: String?): ShellParameter { - this.cwd = cwd - return this - } + fun currentWorkingDirectory(cwd: String?): ShellParameter { + this.cwd = cwd + return this + } - fun initialCommand(initialCommand: String?): ShellParameter { - this.initialCommand = initialCommand - return this - } + fun initialCommand(initialCommand: String?): ShellParameter { + this.initialCommand = initialCommand + return this + } - fun environment(env: Array>?): ShellParameter { - this.env = env - return this - } + fun environment(env: Array>?): ShellParameter { + this.env = env + return this + } - fun callback(callback: TerminalSession.SessionChangedCallback?): ShellParameter { - this.sessionCallback = callback - return this - } + fun callback(callback: TerminalSession.SessionChangedCallback?): ShellParameter { + this.sessionCallback = callback + return this + } - fun systemShell(systemShell: Boolean): ShellParameter { - this.systemShell = systemShell - return this - } + fun systemShell(systemShell: Boolean): ShellParameter { + this.systemShell = systemShell + return this + } - fun profile(shellProfile: ShellProfile): ShellParameter { - this.shellProfile = shellProfile - return this - } + fun profile(shellProfile: ShellProfile): ShellParameter { + this.shellProfile = shellProfile + return this + } - fun session(sessionId: SessionId?): ShellParameter { - this.sessionId = sessionId - return this - } + fun session(sessionId: SessionId?): ShellParameter { + this.sessionId = sessionId + return this + } - fun willCreateNewSession(): Boolean { - return sessionId?.equals(SessionId.NEW_SESSION) ?: true - } + fun willCreateNewSession(): Boolean { + return sessionId?.equals(SessionId.NEW_SESSION) ?: true + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/session/shell/ShellProfile.kt b/app/src/main/java/io/neoterm/frontend/session/shell/ShellProfile.kt index 55eff34..8f2bf0d 100644 --- a/app/src/main/java/io/neoterm/frontend/session/shell/ShellProfile.kt +++ b/app/src/main/java/io/neoterm/frontend/session/shell/ShellProfile.kt @@ -12,76 +12,76 @@ import io.neoterm.frontend.config.NeoPreference * @author kiva */ class ShellProfile : NeoProfile() { - companion object { - const val PROFILE_META_NAME = "profile-shell" + companion object { + const val PROFILE_META_NAME = "profile-shell" - private const val LOGIN_SHELL = "login-shell" - private const val INITIAL_COMMAND = "init-command" - private const val BELL = "bell" - private const val VIBRATE = "vibrate" - private const val EXECVE_WRAPPER = "execve-wrapper" - private const val SPECIAL_VOLUME_KEYS = "special-volume-keys" - private const val AUTO_COMPLETION = "auto-completion" - private const val BACK_KEY_TO_ESC = "back-key-esc" - private const val EXTRA_KEYS = "extra-keys" - private const val FONT = "font" - private const val COLOR_SCHEME = "color-scheme" - private const val WORD_BASED_IME = "word-based-ime" + private const val LOGIN_SHELL = "login-shell" + private const val INITIAL_COMMAND = "init-command" + private const val BELL = "bell" + private const val VIBRATE = "vibrate" + private const val EXECVE_WRAPPER = "execve-wrapper" + private const val SPECIAL_VOLUME_KEYS = "special-volume-keys" + private const val AUTO_COMPLETION = "auto-completion" + private const val BACK_KEY_TO_ESC = "back-key-esc" + private const val EXTRA_KEYS = "extra-keys" + private const val FONT = "font" + private const val COLOR_SCHEME = "color-scheme" + private const val WORD_BASED_IME = "word-based-ime" - fun create() : ShellProfile { - return ShellProfile() - } + fun create(): ShellProfile { + return ShellProfile() } + } - override val profileMetaName = PROFILE_META_NAME + override val profileMetaName = PROFILE_META_NAME - var loginShell = DefaultValues.loginShell - var initialCommand = DefaultValues.initialCommand + var loginShell = DefaultValues.loginShell + var initialCommand = DefaultValues.initialCommand - var enableBell = DefaultValues.enableBell - var enableVibrate = DefaultValues.enableVibrate - var enableExecveWrapper = DefaultValues.enableExecveWrapper - var enableSpecialVolumeKeys = DefaultValues.enableSpecialVolumeKeys - var enableAutoCompletion = DefaultValues.enableAutoCompletion - var enableBackKeyToEscape = DefaultValues.enableBackButtonBeMappedToEscape - var enableExtraKeys = DefaultValues.enableExtraKeys - var enableWordBasedIme = DefaultValues.enableWordBasedIme + var enableBell = DefaultValues.enableBell + var enableVibrate = DefaultValues.enableVibrate + var enableExecveWrapper = DefaultValues.enableExecveWrapper + var enableSpecialVolumeKeys = DefaultValues.enableSpecialVolumeKeys + var enableAutoCompletion = DefaultValues.enableAutoCompletion + var enableBackKeyToEscape = DefaultValues.enableBackButtonBeMappedToEscape + var enableExtraKeys = DefaultValues.enableExtraKeys + var enableWordBasedIme = DefaultValues.enableWordBasedIme - var profileFont: String - var profileColorScheme: String + var profileFont: String + var profileColorScheme: String - init { - val fontComp = ComponentManager.getComponent() - val colorComp = ComponentManager.getComponent() + init { + val fontComp = ComponentManager.getComponent() + val colorComp = ComponentManager.getComponent() - profileFont = fontComp.getCurrentFontName() - profileColorScheme = colorComp.getCurrentColorSchemeName() + profileFont = fontComp.getCurrentFontName() + profileColorScheme = colorComp.getCurrentColorSchemeName() - loginShell = NeoPreference.getLoginShellPath() - initialCommand = NeoPreference.getInitialCommand() - enableBell = NeoPreference.isBellEnabled() - enableVibrate = NeoPreference.isVibrateEnabled() - enableExecveWrapper = NeoPreference.isExecveWrapperEnabled() - enableSpecialVolumeKeys = NeoPreference.isSpecialVolumeKeysEnabled() - enableAutoCompletion = NeoPreference.isAutoCompletionEnabled() - enableBackKeyToEscape = NeoPreference.isBackButtonBeMappedToEscapeEnabled() - enableExtraKeys = NeoPreference.isExtraKeysEnabled() - enableWordBasedIme = NeoPreference.isWordBasedImeEnabled() - } + loginShell = NeoPreference.getLoginShellPath() + initialCommand = NeoPreference.getInitialCommand() + enableBell = NeoPreference.isBellEnabled() + enableVibrate = NeoPreference.isVibrateEnabled() + enableExecveWrapper = NeoPreference.isExecveWrapperEnabled() + enableSpecialVolumeKeys = NeoPreference.isSpecialVolumeKeysEnabled() + enableAutoCompletion = NeoPreference.isAutoCompletionEnabled() + enableBackKeyToEscape = NeoPreference.isBackButtonBeMappedToEscapeEnabled() + enableExtraKeys = NeoPreference.isExtraKeysEnabled() + enableWordBasedIme = NeoPreference.isWordBasedImeEnabled() + } - override fun onConfigLoaded(configVisitor: ConfigVisitor) { - super.onConfigLoaded(configVisitor) - loginShell = configVisitor.getProfileString(LOGIN_SHELL, loginShell) - initialCommand = configVisitor.getProfileString(INITIAL_COMMAND, initialCommand) - enableBell = configVisitor.getProfileBoolean(BELL, enableBell) - enableVibrate = configVisitor.getProfileBoolean(VIBRATE, enableVibrate) - enableExecveWrapper = configVisitor.getProfileBoolean(EXECVE_WRAPPER, enableExecveWrapper) - enableSpecialVolumeKeys = configVisitor.getProfileBoolean(SPECIAL_VOLUME_KEYS, enableSpecialVolumeKeys) - enableAutoCompletion = configVisitor.getProfileBoolean(AUTO_COMPLETION, enableAutoCompletion) - enableBackKeyToEscape = configVisitor.getProfileBoolean(BACK_KEY_TO_ESC, enableBackKeyToEscape) - enableExtraKeys = configVisitor.getProfileBoolean(EXTRA_KEYS, enableExtraKeys) - enableWordBasedIme = configVisitor.getProfileBoolean(WORD_BASED_IME, enableWordBasedIme) - profileFont = configVisitor.getProfileString(FONT, profileFont) - profileColorScheme = configVisitor.getProfileString(COLOR_SCHEME, profileColorScheme) - } + override fun onConfigLoaded(configVisitor: ConfigVisitor) { + super.onConfigLoaded(configVisitor) + loginShell = configVisitor.getProfileString(LOGIN_SHELL, loginShell) + initialCommand = configVisitor.getProfileString(INITIAL_COMMAND, initialCommand) + enableBell = configVisitor.getProfileBoolean(BELL, enableBell) + enableVibrate = configVisitor.getProfileBoolean(VIBRATE, enableVibrate) + enableExecveWrapper = configVisitor.getProfileBoolean(EXECVE_WRAPPER, enableExecveWrapper) + enableSpecialVolumeKeys = configVisitor.getProfileBoolean(SPECIAL_VOLUME_KEYS, enableSpecialVolumeKeys) + enableAutoCompletion = configVisitor.getProfileBoolean(AUTO_COMPLETION, enableAutoCompletion) + enableBackKeyToEscape = configVisitor.getProfileBoolean(BACK_KEY_TO_ESC, enableBackKeyToEscape) + enableExtraKeys = configVisitor.getProfileBoolean(EXTRA_KEYS, enableExtraKeys) + enableWordBasedIme = configVisitor.getProfileBoolean(WORD_BASED_IME, enableWordBasedIme) + profileFont = configVisitor.getProfileString(FONT, profileFont) + profileColorScheme = configVisitor.getProfileString(COLOR_SCHEME, profileColorScheme) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/session/shell/ShellTermSession.kt b/app/src/main/java/io/neoterm/frontend/session/shell/ShellTermSession.kt index b0f2ba4..11e19b8 100644 --- a/app/src/main/java/io/neoterm/frontend/session/shell/ShellTermSession.kt +++ b/app/src/main/java/io/neoterm/frontend/session/shell/ShellTermSession.kt @@ -11,231 +11,237 @@ import java.io.File /** * @author kiva */ -open class ShellTermSession private constructor(shellPath: String, cwd: String, - args: Array, env: Array, - changeCallback: SessionChangedCallback, - private val initialCommand: String?, - val shellProfile: ShellProfile) - : TerminalSession(shellPath, cwd, args, env, changeCallback) { +open class ShellTermSession private constructor( + shellPath: String, cwd: String, + args: Array, env: Array, + changeCallback: SessionChangedCallback, + private val initialCommand: String?, + val shellProfile: ShellProfile +) : TerminalSession(shellPath, cwd, args, env, changeCallback) { - var exitPrompt = App.get().getString(R.string.process_exit_prompt) + var exitPrompt = App.get().getString(R.string.process_exit_prompt) - override fun initializeEmulator(columns: Int, rows: Int) { - super.initializeEmulator(columns, rows) - sendInitialCommand(shellProfile.initialCommand) - sendInitialCommand(initialCommand) + override fun initializeEmulator(columns: Int, rows: Int) { + super.initializeEmulator(columns, rows) + sendInitialCommand(shellProfile.initialCommand) + sendInitialCommand(initialCommand) + } + + override fun getExitDescription(exitCode: Int): String { + val builder = StringBuilder("\r\n[") + val context = App.get() + builder.append(context.getString(R.string.process_exit_info)) + if (exitCode > 0) { + // Non-zero process exit. + builder.append(" (") + builder.append(context.getString(R.string.process_exit_code, exitCode)) + builder.append(")") + } else if (exitCode < 0) { + // Negated signal. + builder.append(" (") + builder.append(context.getString(R.string.process_exit_signal, -exitCode)) + builder.append(")") + } + builder.append(" - $exitPrompt]") + return builder.toString() + } + + private fun sendInitialCommand(command: String?) { + if (command?.isNotEmpty() == true) { + write(command + '\r') + } + } + + class Builder { + private var executablePath: String? = null + private var cwd: String? = null + private var args: MutableList? = null + private var env: MutableList>? = null + private var changeCallback: SessionChangedCallback? = null + private var systemShell = false + private var initialCommand: String? = null + private var shellProfile = ShellProfile() + + fun profile(shellProfile: ShellProfile?): Builder { + if (shellProfile != null) { + this.shellProfile = shellProfile + } + return this } - override fun getExitDescription(exitCode: Int): String { - val builder = StringBuilder("\r\n[") - val context = App.get() - builder.append(context.getString(R.string.process_exit_info)) - if (exitCode > 0) { - // Non-zero process exit. - builder.append(" (") - builder.append(context.getString(R.string.process_exit_code, exitCode)) - builder.append(")") - } else if (exitCode < 0) { - // Negated signal. - builder.append(" (") - builder.append(context.getString(R.string.process_exit_signal, -exitCode)) - builder.append(")") - } - builder.append(" - $exitPrompt]") - return builder.toString() + fun initialCommand(command: String?): Builder { + this.initialCommand = command + return this } - private fun sendInitialCommand(command: String?) { - if (command?.isNotEmpty() == true) { - write(command + '\r') - } + fun executablePath(shell: String?): Builder { + this.executablePath = shell + return this } - class Builder { - private var executablePath: String? = null - private var cwd: String? = null - private var args: MutableList? = null - private var env: MutableList>? = null - private var changeCallback: SessionChangedCallback? = null - private var systemShell = false - private var initialCommand: String? = null - private var shellProfile = ShellProfile() - - fun profile(shellProfile: ShellProfile?): Builder { - if (shellProfile != null) { - this.shellProfile = shellProfile - } - return this - } - - fun initialCommand(command: String?): Builder { - this.initialCommand = command - return this - } - - fun executablePath(shell: String?): Builder { - this.executablePath = shell - return this - } - - fun currentWorkingDirectory(cwd: String?): Builder { - this.cwd = cwd - return this - } - - fun arg(arg: String?): Builder { - if (arg != null) { - if (args == null) { - args = mutableListOf(arg) - } else { - args!!.add(arg) - } - } else { - this.args = null - } - return this - } - - fun argArray(args: Array?): Builder { - if (args != null) { - if (args.isEmpty()) { - this.args = null - return this - } - args.forEach { arg(it) } - } else { - this.args = null - } - return this - } - - fun env(env: Pair?): Builder { - if (env != null) { - if (this.env == null) { - this.env = mutableListOf(env) - } else { - this.env!!.add(env) - } - } else { - this.env = null - } - return this - } - - fun envArray(env: Array>?): Builder { - if (env != null) { - if (env.isEmpty()) { - this.env = null - return this - } - env.forEach { env(it) } - } else { - this.env = null - } - return this - } - - fun callback(callback: SessionChangedCallback?): Builder { - this.changeCallback = callback - return this - } - - fun systemShell(systemShell: Boolean): Builder { - this.systemShell = systemShell - return this - } - - fun create(context: Context): ShellTermSession { - val cwd = this.cwd ?: NeoTermPath.HOME_PATH - - val shell = this.executablePath ?: - if (systemShell) - "/system/bin/sh" - else - shellProfile.loginShell - - val args = this.args ?: mutableListOf(shell) - val env = transformEnvironment(this.env) ?: buildEnvironment(cwd, systemShell) - val callback = changeCallback ?: TermSessionCallback() - return ShellTermSession(shell, cwd, args.toTypedArray(), env, callback, - initialCommand ?: "", shellProfile) - } - - private fun transformEnvironment(env: MutableList>?): Array? { - if (env == null) { - return null - } - - val result = mutableListOf() - return env.mapTo(result, { "${it.first}=${it.second}" }) - .toTypedArray() - } - - - private fun buildEnvironment(cwd: String?, systemShell: Boolean): Array { - val selectedCwd = cwd ?: NeoTermPath.HOME_PATH - File(NeoTermPath.HOME_PATH).mkdirs() - - val termEnv = "TERM=xterm-256color" - val homeEnv = "HOME=" + NeoTermPath.HOME_PATH - val prefixEnv = "PREFIX=" + NeoTermPath.USR_PATH - val androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT") - val androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA") - val externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE") - val colorterm = "COLORTERM=truecolor" - - // PY Trade: Some programs support NeoTerm in a special way. - val neotermIdEnv = "__NEOTERM=1" - val originPathEnv = "__NEOTERM_ORIGIN_PATH=" + buildOriginPathEnv() - val originLdEnv = "__NEOTERM_ORIGIN_LD_LIBRARY_PATH=" + buildOriginLdLibEnv() - - return if (systemShell) { - val pathEnv = "PATH=" + System.getenv("PATH") - arrayOf(termEnv, homeEnv, androidRootEnv, androidDataEnv, - externalStorageEnv, pathEnv, neotermIdEnv, prefixEnv, - originLdEnv, originPathEnv, colorterm) - - } else { - val ps1Env = "PS1=$ " - val langEnv = "LANG=en_US.UTF-8" - val pathEnv = "PATH=" + buildPathEnv() - val ldEnv = "LD_LIBRARY_PATH=" + buildLdLibraryEnv() - val pwdEnv = "PWD=$selectedCwd" - val tmpdirEnv = "TMPDIR=${NeoTermPath.USR_PATH}/tmp" - - - // execve(2) wrapper to avoid incorrect shebang - val ldPreloadEnv = if (shellProfile.enableExecveWrapper) { - "LD_PRELOAD=${App.get().applicationInfo.nativeLibraryDir}/libnexec.so" - } else { - "" - } - - arrayOf(termEnv, homeEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, - androidRootEnv, androidDataEnv, externalStorageEnv, - tmpdirEnv, neotermIdEnv, originPathEnv, originLdEnv, - ldPreloadEnv, prefixEnv, colorterm) - } - .filter { it.isNotEmpty() } - .toTypedArray() - } - - private fun buildOriginPathEnv(): String { - val path = System.getenv("PATH") - return path ?: "" - } - - private fun buildOriginLdLibEnv(): String { - val path = System.getenv("LD_LIBRARY_PATH") - return path ?: "" - } - - private fun buildLdLibraryEnv(): String { - return "${NeoTermPath.USR_PATH}/lib" - } - - private fun buildPathEnv(): String { - return "${NeoTermPath.USR_PATH}/bin:${NeoTermPath.USR_PATH}/bin/applets" - } + fun currentWorkingDirectory(cwd: String?): Builder { + this.cwd = cwd + return this } + + fun arg(arg: String?): Builder { + if (arg != null) { + if (args == null) { + args = mutableListOf(arg) + } else { + args!!.add(arg) + } + } else { + this.args = null + } + return this + } + + fun argArray(args: Array?): Builder { + if (args != null) { + if (args.isEmpty()) { + this.args = null + return this + } + args.forEach { arg(it) } + } else { + this.args = null + } + return this + } + + fun env(env: Pair?): Builder { + if (env != null) { + if (this.env == null) { + this.env = mutableListOf(env) + } else { + this.env!!.add(env) + } + } else { + this.env = null + } + return this + } + + fun envArray(env: Array>?): Builder { + if (env != null) { + if (env.isEmpty()) { + this.env = null + return this + } + env.forEach { env(it) } + } else { + this.env = null + } + return this + } + + fun callback(callback: SessionChangedCallback?): Builder { + this.changeCallback = callback + return this + } + + fun systemShell(systemShell: Boolean): Builder { + this.systemShell = systemShell + return this + } + + fun create(context: Context): ShellTermSession { + val cwd = this.cwd ?: NeoTermPath.HOME_PATH + + val shell = this.executablePath ?: if (systemShell) + "/system/bin/sh" + else + shellProfile.loginShell + + val args = this.args ?: mutableListOf(shell) + val env = transformEnvironment(this.env) ?: buildEnvironment(cwd, systemShell) + val callback = changeCallback ?: TermSessionCallback() + return ShellTermSession( + shell, cwd, args.toTypedArray(), env, callback, + initialCommand ?: "", shellProfile + ) + } + + private fun transformEnvironment(env: MutableList>?): Array? { + if (env == null) { + return null + } + + val result = mutableListOf() + return env.mapTo(result, { "${it.first}=${it.second}" }) + .toTypedArray() + } + + + private fun buildEnvironment(cwd: String?, systemShell: Boolean): Array { + val selectedCwd = cwd ?: NeoTermPath.HOME_PATH + File(NeoTermPath.HOME_PATH).mkdirs() + + val termEnv = "TERM=xterm-256color" + val homeEnv = "HOME=" + NeoTermPath.HOME_PATH + val prefixEnv = "PREFIX=" + NeoTermPath.USR_PATH + val androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT") + val androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA") + val externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE") + val colorterm = "COLORTERM=truecolor" + + // PY Trade: Some programs support NeoTerm in a special way. + val neotermIdEnv = "__NEOTERM=1" + val originPathEnv = "__NEOTERM_ORIGIN_PATH=" + buildOriginPathEnv() + val originLdEnv = "__NEOTERM_ORIGIN_LD_LIBRARY_PATH=" + buildOriginLdLibEnv() + + return if (systemShell) { + val pathEnv = "PATH=" + System.getenv("PATH") + arrayOf( + termEnv, homeEnv, androidRootEnv, androidDataEnv, + externalStorageEnv, pathEnv, neotermIdEnv, prefixEnv, + originLdEnv, originPathEnv, colorterm + ) + + } else { + val ps1Env = "PS1=$ " + val langEnv = "LANG=en_US.UTF-8" + val pathEnv = "PATH=" + buildPathEnv() + val ldEnv = "LD_LIBRARY_PATH=" + buildLdLibraryEnv() + val pwdEnv = "PWD=$selectedCwd" + val tmpdirEnv = "TMPDIR=${NeoTermPath.USR_PATH}/tmp" + + + // execve(2) wrapper to avoid incorrect shebang + val ldPreloadEnv = if (shellProfile.enableExecveWrapper) { + "LD_PRELOAD=${App.get().applicationInfo.nativeLibraryDir}/libnexec.so" + } else { + "" + } + + arrayOf( + termEnv, homeEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, + androidRootEnv, androidDataEnv, externalStorageEnv, + tmpdirEnv, neotermIdEnv, originPathEnv, originLdEnv, + ldPreloadEnv, prefixEnv, colorterm + ) + } + .filter { it.isNotEmpty() } + .toTypedArray() + } + + private fun buildOriginPathEnv(): String { + val path = System.getenv("PATH") + return path ?: "" + } + + private fun buildOriginLdLibEnv(): String { + val path = System.getenv("LD_LIBRARY_PATH") + return path ?: "" + } + + private fun buildLdLibraryEnv(): String { + return "${NeoTermPath.USR_PATH}/lib" + } + + private fun buildPathEnv(): String { + return "${NeoTermPath.USR_PATH}/bin:${NeoTermPath.USR_PATH}/bin/applets" + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/session/shell/client/BasicSessionCallback.kt b/app/src/main/java/io/neoterm/frontend/session/shell/client/BasicSessionCallback.kt index c4026cf..d0bfdb4 100644 --- a/app/src/main/java/io/neoterm/frontend/session/shell/client/BasicSessionCallback.kt +++ b/app/src/main/java/io/neoterm/frontend/session/shell/client/BasicSessionCallback.kt @@ -7,27 +7,27 @@ import io.neoterm.frontend.terminal.TerminalView * @author kiva */ open class BasicSessionCallback(var terminalView: TerminalView) : TerminalSession.SessionChangedCallback { - override fun onTextChanged(changedSession: TerminalSession?) { - if (changedSession != null) { - terminalView.onScreenUpdated() - } + override fun onTextChanged(changedSession: TerminalSession?) { + if (changedSession != null) { + terminalView.onScreenUpdated() } + } - override fun onTitleChanged(changedSession: TerminalSession?) { - } + override fun onTitleChanged(changedSession: TerminalSession?) { + } - override fun onSessionFinished(finishedSession: TerminalSession?) { - } + override fun onSessionFinished(finishedSession: TerminalSession?) { + } - override fun onClipboardText(session: TerminalSession?, text: String?) { - } + override fun onClipboardText(session: TerminalSession?, text: String?) { + } - override fun onBell(session: TerminalSession?) { - } + override fun onBell(session: TerminalSession?) { + } - override fun onColorsChanged(session: TerminalSession?) { - if (session != null) { - terminalView.onScreenUpdated() - } + override fun onColorsChanged(session: TerminalSession?) { + if (session != null) { + terminalView.onScreenUpdated() } + } } diff --git a/app/src/main/java/io/neoterm/frontend/session/shell/client/BasicViewClient.kt b/app/src/main/java/io/neoterm/frontend/session/shell/client/BasicViewClient.kt index 36d3fe1..b1bd0d6 100644 --- a/app/src/main/java/io/neoterm/frontend/session/shell/client/BasicViewClient.kt +++ b/app/src/main/java/io/neoterm/frontend/session/shell/client/BasicViewClient.kt @@ -13,52 +13,52 @@ import io.neoterm.frontend.terminal.TerminalViewClient * @author kiva */ class BasicViewClient(val terminalView: TerminalView) : TerminalViewClient { - override fun onScale(scale: Float): Float { - if (scale < 0.9f || scale > 1.1f) { - val increase = scale > 1f - val changedSize = (if (increase) 1 else -1) * 2 - val fontSize = NeoPreference.validateFontSize(terminalView.textSize + changedSize) - terminalView.textSize = fontSize - return 1.0f - } - return scale + override fun onScale(scale: Float): Float { + if (scale < 0.9f || scale > 1.1f) { + val increase = scale > 1f + val changedSize = (if (increase) 1 else -1) * 2 + val fontSize = NeoPreference.validateFontSize(terminalView.textSize + changedSize) + terminalView.textSize = fontSize + return 1.0f } + return scale + } - override fun onSingleTapUp(e: MotionEvent?) { - if (terminalView.isFocusable && terminalView.isFocusableInTouchMode) { - (terminalView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) - .showSoftInput(terminalView, InputMethodManager.SHOW_IMPLICIT) - } + override fun onSingleTapUp(e: MotionEvent?) { + if (terminalView.isFocusable && terminalView.isFocusableInTouchMode) { + (terminalView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) + .showSoftInput(terminalView, InputMethodManager.SHOW_IMPLICIT) } + } - override fun shouldBackButtonBeMappedToEscape(): Boolean { - return false - } + override fun shouldBackButtonBeMappedToEscape(): Boolean { + return false + } - override fun copyModeChanged(copyMode: Boolean) { - } + override fun copyModeChanged(copyMode: Boolean) { + } - override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean { - return false - } + override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean { + return false + } - override fun onKeyUp(keyCode: Int, e: KeyEvent?): Boolean { - return false - } + override fun onKeyUp(keyCode: Int, e: KeyEvent?): Boolean { + return false + } - override fun readControlKey(): Boolean { - return false - } + override fun readControlKey(): Boolean { + return false + } - override fun readAltKey(): Boolean { - return false - } + override fun readAltKey(): Boolean { + return false + } - override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean { - return false - } + override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean { + return false + } - override fun onLongPress(event: MotionEvent?): Boolean { - return false - } + override fun onLongPress(event: MotionEvent?): Boolean { + return false + } } diff --git a/app/src/main/java/io/neoterm/frontend/session/shell/client/BellController.kt b/app/src/main/java/io/neoterm/frontend/session/shell/client/BellController.kt index d59f786..57e01ab 100644 --- a/app/src/main/java/io/neoterm/frontend/session/shell/client/BellController.kt +++ b/app/src/main/java/io/neoterm/frontend/session/shell/client/BellController.kt @@ -10,32 +10,32 @@ import io.neoterm.frontend.session.shell.ShellTermSession * @author kiva */ class BellController constructor() { - companion object { - private val BELL_DELAY_MS = 100 + companion object { + private val BELL_DELAY_MS = 100 + } + + private var bellId: Int = 0 + private var soundPool: SoundPool? = null + private var lastBellTime = 0L + + fun bellOrVibrate(context: Context, session: ShellTermSession) { + val currentTime = System.currentTimeMillis() + if (currentTime - lastBellTime < BELL_DELAY_MS) { + return + } + lastBellTime = currentTime + + if (session.shellProfile.enableBell) { + if (soundPool == null) { + soundPool = SoundPool.Builder().setMaxStreams(1).build() + bellId = soundPool!!.load(context, R.raw.bell, 1) + } + soundPool?.play(bellId, 1f, 1f, 0, 0, 1f) } - private var bellId: Int = 0 - private var soundPool: SoundPool? = null - private var lastBellTime = 0L - - fun bellOrVibrate(context: Context, session: ShellTermSession) { - val currentTime = System.currentTimeMillis() - if (currentTime - lastBellTime < BELL_DELAY_MS) { - return - } - lastBellTime = currentTime - - if (session.shellProfile.enableBell) { - if (soundPool == null) { - soundPool = SoundPool.Builder().setMaxStreams(1).build() - bellId = soundPool!!.load(context, R.raw.bell, 1) - } - soundPool?.play(bellId, 1f, 1f, 0, 0, 1f) - } - - if (session.shellProfile.enableVibrate) { - val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator - vibrator.vibrate(100) - } + if (session.shellProfile.enableVibrate) { + val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + vibrator.vibrate(100) } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/session/shell/client/TermCompleteListener.kt b/app/src/main/java/io/neoterm/frontend/session/shell/client/TermCompleteListener.kt index a588c8e..538d4c3 100644 --- a/app/src/main/java/io/neoterm/frontend/session/shell/client/TermCompleteListener.kt +++ b/app/src/main/java/io/neoterm/frontend/session/shell/client/TermCompleteListener.kt @@ -9,7 +9,6 @@ import io.neoterm.frontend.completion.listener.OnCandidateSelectedListener import io.neoterm.frontend.completion.model.CompletionCandidate import io.neoterm.frontend.completion.model.CompletionResult import io.neoterm.frontend.completion.view.CandidatePopupWindow -import io.neoterm.frontend.logging.NLog import io.neoterm.frontend.terminal.TerminalView import java.util.* @@ -17,148 +16,150 @@ import java.util.* * @author kiva */ class TermCompleteListener(var terminalView: TerminalView?) : OnAutoCompleteListener, OnCandidateSelectedListener { - private val inputStack = Stack() - private var popupWindow: CandidatePopupWindow? = null - private var lastCompletedIndex = 0 + private val inputStack = Stack() + private var popupWindow: CandidatePopupWindow? = null + private var lastCompletedIndex = 0 - override fun onKeyCode(keyCode: Int, keyMod: Int) { - when (keyCode) { - KeyEvent.KEYCODE_DEL -> { - popChar() - fixLastCompletedIndex() - triggerCompletion() - } - - KeyEvent.KEYCODE_ENTER -> { - clearChars() - popupWindow?.dismiss() - } - } - } - - private fun fixLastCompletedIndex() { - val currentText = getCurrentEditingText() - lastCompletedIndex = minOf(lastCompletedIndex, currentText.length - 1) - } - - override fun onCompletionRequired(newText: String?) { - if (newText == null || newText.isEmpty()) { - return - } - pushString(newText) + override fun onKeyCode(keyCode: Int, keyMod: Int) { + when (keyCode) { + KeyEvent.KEYCODE_DEL -> { + popChar() + fixLastCompletedIndex() triggerCompletion() - } + } - override fun onCleanUp() { + KeyEvent.KEYCODE_ENTER -> { + clearChars() popupWindow?.dismiss() - popupWindow?.cleanup() - popupWindow = null - terminalView = null + } + } + } + + private fun fixLastCompletedIndex() { + val currentText = getCurrentEditingText() + lastCompletedIndex = minOf(lastCompletedIndex, currentText.length - 1) + } + + override fun onCompletionRequired(newText: String?) { + if (newText == null || newText.isEmpty()) { + return + } + pushString(newText) + triggerCompletion() + } + + override fun onCleanUp() { + popupWindow?.dismiss() + popupWindow?.cleanup() + popupWindow = null + terminalView = null + } + + override fun onFinishCompletion(): Boolean { + val popWindow = popupWindow ?: return false + + if (popWindow.isShowing()) { + popWindow.dismiss() + return true + } + return false + } + + override fun onCandidateSelected(candidate: CompletionCandidate) { + val session = terminalView?.currentSession ?: return + val textNeedCompletion = getCurrentEditingText().substring(lastCompletedIndex + 1) + val newText = candidate.completeString + + val deleteLength = newText.indexOf(textNeedCompletion) + textNeedCompletion.length + if (deleteLength > 0) { + for (i in 0 until deleteLength) { + session.write("\b") + popChar() + } } - override fun onFinishCompletion(): Boolean { - val popWindow = popupWindow ?: return false - - if (popWindow.isShowing()) { - popWindow.dismiss() - return true - } - return false + if (BuildConfig.DEBUG) { + Log.e( + "NeoTerm-AC", "currentEditing: $textNeedCompletion, " + + "deleteLength: $deleteLength, completeString: $newText" + ) } - override fun onCandidateSelected(candidate: CompletionCandidate) { - val session = terminalView?.currentSession ?: return - val textNeedCompletion = getCurrentEditingText().substring(lastCompletedIndex + 1) - val newText = candidate.completeString + pushString(newText) + session.write(newText) + // Trigger next completion + lastCompletedIndex = inputStack.size + triggerCompletion() + } - val deleteLength = newText.indexOf(textNeedCompletion) + textNeedCompletion.length - if (deleteLength > 0) { - for (i in 0 until deleteLength) { - session.write("\b") - popChar() - } - } - - if (BuildConfig.DEBUG) { - Log.e("NeoTerm-AC", "currentEditing: $textNeedCompletion, " + - "deleteLength: $deleteLength, completeString: $newText") - } - - pushString(newText) - session.write(newText) - // Trigger next completion - lastCompletedIndex = inputStack.size - triggerCompletion() + private fun triggerCompletion() { + val text = getCurrentEditingText() + if (text.isEmpty()) { + return } - private fun triggerCompletion() { - val text = getCurrentEditingText() - if (text.isEmpty()) { - return - } + val result = CompletionManager.tryCompleteFor(text) + if (!result.hasResult()) { + // A provider accepted the task + // But no candidates are provided + // Give it zero angrily! + result.markScore(0) + onFinishCompletion() + return + } + showAutoCompleteCandidates(result) + } - val result = CompletionManager.tryCompleteFor(text) - if (!result.hasResult()) { - // A provider accepted the task - // But no candidates are provided - // Give it zero angrily! - result.markScore(0) - onFinishCompletion() - return - } - showAutoCompleteCandidates(result) + private fun showAutoCompleteCandidates(result: CompletionResult) { + val termView = terminalView + var popWindow = popupWindow + + if (termView == null) { + return } - private fun showAutoCompleteCandidates(result: CompletionResult) { - val termView = terminalView - var popWindow = popupWindow - - if (termView == null) { - return - } - - if (popWindow == null) { - popWindow = CandidatePopupWindow(termView.context) - popWindow.onCandidateSelectedListener = this - this.popupWindow = popWindow - } - - popWindow.candidates = result.candidates - popWindow.show(termView) + if (popWindow == null) { + popWindow = CandidatePopupWindow(termView.context) + popWindow.onCandidateSelectedListener = this + this.popupWindow = popWindow } - private fun getCurrentEditingText(): String { - val builder = StringBuilder() - val size = inputStack.size - var start = inputStack.lastIndexOf(' ') - if (start < 0) { - // Yes, it is -1, we will do `start + 1` below. - start = -1 - } + popWindow.candidates = result.candidates + popWindow.show(termView) + } - IntRange(start + 1, size - 1) - .map { inputStack[it] } - .takeWhile { !(it == 0.toChar() || it == ' ') } - .forEach { builder.append(it) } - return builder.toString() + private fun getCurrentEditingText(): String { + val builder = StringBuilder() + val size = inputStack.size + var start = inputStack.lastIndexOf(' ') + if (start < 0) { + // Yes, it is -1, we will do `start + 1` below. + start = -1 } - private fun clearChars() { - inputStack.clear() - lastCompletedIndex = 0 - } + IntRange(start + 1, size - 1) + .map { inputStack[it] } + .takeWhile { !(it == 0.toChar() || it == ' ') } + .forEach { builder.append(it) } + return builder.toString() + } - private fun popChar() { - if (inputStack.isNotEmpty()) { - inputStack.pop() - } - } + private fun clearChars() { + inputStack.clear() + lastCompletedIndex = 0 + } - private fun pushString(string: String) { - string.toCharArray().forEach { pushChar(it) } + private fun popChar() { + if (inputStack.isNotEmpty()) { + inputStack.pop() } + } - private fun pushChar(char: Char) { - inputStack.push(char) - } + private fun pushString(string: String) { + string.toCharArray().forEach { pushChar(it) } + } + + private fun pushChar(char: Char) { + inputStack.push(char) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/session/shell/client/TermSessionCallback.kt b/app/src/main/java/io/neoterm/frontend/session/shell/client/TermSessionCallback.kt index 17e27c4..53f4ddf 100644 --- a/app/src/main/java/io/neoterm/frontend/session/shell/client/TermSessionCallback.kt +++ b/app/src/main/java/io/neoterm/frontend/session/shell/client/TermSessionCallback.kt @@ -10,47 +10,47 @@ import io.neoterm.frontend.session.shell.ShellTermSession * @author kiva */ class TermSessionCallback : TerminalSession.SessionChangedCallback { - var termSessionData: TermSessionData? = null + var termSessionData: TermSessionData? = null - var bellController: BellController? = null + var bellController: BellController? = null - override fun onTextChanged(changedSession: TerminalSession?) { - termSessionData?.termView?.onScreenUpdated() + override fun onTextChanged(changedSession: TerminalSession?) { + termSessionData?.termView?.onScreenUpdated() + } + + override fun onTitleChanged(changedSession: TerminalSession?) { + if (changedSession?.title != null) { + termSessionData?.termUI?.requireUpdateTitle(changedSession.title) + } + } + + override fun onSessionFinished(finishedSession: TerminalSession?) { + termSessionData?.termUI?.requireOnSessionFinished() + } + + override fun onClipboardText(session: TerminalSession?, text: String?) { + val termView = termSessionData?.termView + if (termView != null) { + val clipboard = termView.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + clipboard.primaryClip = ClipData.newPlainText("", text) + } + } + + override fun onBell(session: TerminalSession?) { + val termView = termSessionData?.termView ?: return + val shellSession = session as ShellTermSession + + if (bellController == null) { + bellController = BellController() } - override fun onTitleChanged(changedSession: TerminalSession?) { - if (changedSession?.title != null) { - termSessionData?.termUI?.requireUpdateTitle(changedSession.title) - } - } - - override fun onSessionFinished(finishedSession: TerminalSession?) { - termSessionData?.termUI?.requireOnSessionFinished() - } - - override fun onClipboardText(session: TerminalSession?, text: String?) { - val termView = termSessionData?.termView - if (termView != null) { - val clipboard = termView.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - clipboard.primaryClip = ClipData.newPlainText("", text) - } - } - - override fun onBell(session: TerminalSession?) { - val termView = termSessionData?.termView ?: return - val shellSession = session as ShellTermSession - - if (bellController == null) { - bellController = BellController() - } - - bellController?.bellOrVibrate(termView.context, shellSession) - } - - override fun onColorsChanged(session: TerminalSession?) { - val termView = termSessionData?.termView - if (session != null && termView != null) { - termView.onScreenUpdated() - } + bellController?.bellOrVibrate(termView.context, shellSession) + } + + override fun onColorsChanged(session: TerminalSession?) { + val termView = termSessionData?.termView + if (session != null && termView != null) { + termView.onScreenUpdated() } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/session/shell/client/TermSessionData.kt b/app/src/main/java/io/neoterm/frontend/session/shell/client/TermSessionData.kt index 01fcb8a..6e940e7 100644 --- a/app/src/main/java/io/neoterm/frontend/session/shell/client/TermSessionData.kt +++ b/app/src/main/java/io/neoterm/frontend/session/shell/client/TermSessionData.kt @@ -11,47 +11,51 @@ import io.neoterm.frontend.terminal.extrakey.ExtraKeysView * @author kiva */ class TermSessionData { - var termSession: TerminalSession? = null - var sessionCallback: TermSessionCallback? = null - var viewClient: TermViewClient? = null - var onAutoCompleteListener: OnAutoCompleteListener? = null + var termSession: TerminalSession? = null + var sessionCallback: TermSessionCallback? = null + var viewClient: TermViewClient? = null + var onAutoCompleteListener: OnAutoCompleteListener? = null - var termUI: TermUiPresenter? = null - var termView: TerminalView? = null - var extraKeysView: ExtraKeysView? = null + var termUI: TermUiPresenter? = null + var termView: TerminalView? = null + var extraKeysView: ExtraKeysView? = null - var profile: ShellProfile? = null + var profile: ShellProfile? = null - fun cleanup() { - onAutoCompleteListener?.onCleanUp() - onAutoCompleteListener = null + fun cleanup() { + onAutoCompleteListener?.onCleanUp() + onAutoCompleteListener = null - sessionCallback?.termSessionData = null - viewClient?.termSessionData = null + sessionCallback?.termSessionData = null + viewClient?.termSessionData = null - termUI = null - termView = null - extraKeysView = null - termSession = null + termUI = null + termView = null + extraKeysView = null + termSession = null - profile = null + profile = null + } + + fun initializeSessionWith( + session: TerminalSession, + sessionCallback: TermSessionCallback?, + viewClient: TermViewClient? + ) { + this.termSession = session + this.sessionCallback = sessionCallback + this.viewClient = viewClient + this.sessionCallback?.termSessionData = this + this.viewClient?.termSessionData = this + + if (session is ShellTermSession) { + profile = session.shellProfile } + } - fun initializeSessionWith(session: TerminalSession, sessionCallback: TermSessionCallback?, viewClient: TermViewClient?) { - this.termSession = session - this.sessionCallback = sessionCallback - this.viewClient = viewClient - this.sessionCallback?.termSessionData = this - this.viewClient?.termSessionData = this - - if (session is ShellTermSession) { - profile = session.shellProfile - } - } - - fun initializeViewWith(termUI: TermUiPresenter?, termView: TerminalView?, eks: ExtraKeysView?) { - this.termUI = termUI - this.termView = termView - this.extraKeysView = eks - } + fun initializeViewWith(termUI: TermUiPresenter?, termView: TerminalView?, eks: ExtraKeysView?) { + this.termUI = termUI + this.termView = termView + this.extraKeysView = eks + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/session/shell/client/TermUiPresenter.kt b/app/src/main/java/io/neoterm/frontend/session/shell/client/TermUiPresenter.kt index f99718c..b1c1ea3 100644 --- a/app/src/main/java/io/neoterm/frontend/session/shell/client/TermUiPresenter.kt +++ b/app/src/main/java/io/neoterm/frontend/session/shell/client/TermUiPresenter.kt @@ -4,15 +4,15 @@ package io.neoterm.frontend.session.shell.client * @author kiva */ interface TermUiPresenter { - fun requireClose() - fun requireToggleFullScreen() - fun requirePaste() - fun requireUpdateTitle(title: String?) - fun requireOnSessionFinished() - fun requireHideIme() - fun requireFinishAutoCompletion(): Boolean - fun requireCreateNew() - fun requireSwitchToPrevious() - fun requireSwitchToNext() - fun requireSwitchTo(index: Int) + fun requireClose() + fun requireToggleFullScreen() + fun requirePaste() + fun requireUpdateTitle(title: String?) + fun requireOnSessionFinished() + fun requireHideIme() + fun requireFinishAutoCompletion(): Boolean + fun requireCreateNew() + fun requireSwitchToPrevious() + fun requireSwitchToNext() + fun requireSwitchTo(index: Int) } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/session/shell/client/TermViewClient.kt b/app/src/main/java/io/neoterm/frontend/session/shell/client/TermViewClient.kt index d4647ca..5392031 100644 --- a/app/src/main/java/io/neoterm/frontend/session/shell/client/TermViewClient.kt +++ b/app/src/main/java/io/neoterm/frontend/session/shell/client/TermViewClient.kt @@ -20,239 +20,251 @@ import io.neoterm.frontend.terminal.TerminalViewClient * @author kiva */ class TermViewClient(val context: Context) : TerminalViewClient { - private var mVirtualControlKeyDown: Boolean = false - private var mVirtualFnKeyDown: Boolean = false - private var lastTitle: String = "" + private var mVirtualControlKeyDown: Boolean = false + private var mVirtualFnKeyDown: Boolean = false + private var lastTitle: String = "" - var termSessionData: TermSessionData? = null + var termSessionData: TermSessionData? = null - override fun onScale(scale: Float): Float { - if (scale < 0.9f || scale > 1.1f) { - val increase = scale > 1f - changeFontSize(increase) - return 1.0f - } - return scale + override fun onScale(scale: Float): Float { + if (scale < 0.9f || scale > 1.1f) { + val increase = scale > 1f + changeFontSize(increase) + return 1.0f + } + return scale + } + + override fun onSingleTapUp(e: MotionEvent?) { + val termView = termSessionData?.termView ?: return + (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) + .showSoftInput(termView, InputMethodManager.SHOW_IMPLICIT) + } + + override fun shouldBackButtonBeMappedToEscape(): Boolean { + val shellSession = termSessionData?.termSession as ShellTermSession? ?: return false + return shellSession.shellProfile.enableBackKeyToEscape + } + + override fun copyModeChanged(copyMode: Boolean) { + // TODO + } + + override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean { + if (handleVirtualKeys(keyCode, e, true)) { + return true } - override fun onSingleTapUp(e: MotionEvent?) { - val termView = termSessionData?.termView ?: return - (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) - .showSoftInput(termView, InputMethodManager.SHOW_IMPLICIT) - } + val termUI = termSessionData?.termUI - override fun shouldBackButtonBeMappedToEscape(): Boolean { - val shellSession = termSessionData?.termSession as ShellTermSession? ?: return false - return shellSession.shellProfile.enableBackKeyToEscape - } - - override fun copyModeChanged(copyMode: Boolean) { - // TODO - } - - override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean { - if (handleVirtualKeys(keyCode, e, true)) { - return true - } - - val termUI = termSessionData?.termUI - - when (keyCode) { - KeyEvent.KEYCODE_ENTER -> { - if (e?.action == KeyEvent.ACTION_DOWN && session?.isRunning == false) { - termUI?.requireClose() - return true - } - return false - } - KeyEvent.KEYCODE_BACK -> { - if (e?.action == KeyEvent.ACTION_DOWN) { - return termUI?.requireFinishAutoCompletion() ?: false - } - return false - } - } - - // TODO 自定义快捷键 - if (e != null && e.isCtrlPressed && e.isShiftPressed) { - // Get the unmodified code point: - val unicodeChar = e.getUnicodeChar(0).toChar() - - when (unicodeChar) { - 'v' -> termUI?.requirePaste() - 'n' -> termUI?.requireCreateNew() - 'z' -> termUI?.requireSwitchToPrevious() - 'x' -> termUI?.requireSwitchToNext() - 'f' -> termUI?.requireToggleFullScreen() - '-' -> changeFontSize(false) - '+' -> changeFontSize(true) - } - - // 当要触发 NeoTerm 快捷键时,屏蔽所有终端处理key - return true - } else if (e != null && e.isAltPressed) { - // Get the unmodified code point: - val unicodeChar = e.getUnicodeChar(0).toChar() - if (unicodeChar !in ('1'..'9')) { - return false - } - - // Use Alt + num to switch sessions - val sessionIndex = unicodeChar.toInt() - '0'.toInt() - termUI?.requireSwitchTo(sessionIndex) - - // 当要触发 NeoTerm 快捷键时,屏蔽所有终端处理key - return true + when (keyCode) { + KeyEvent.KEYCODE_ENTER -> { + if (e?.action == KeyEvent.ACTION_DOWN && session?.isRunning == false) { + termUI?.requireClose() + return true } return false - } - - override fun onKeyUp(keyCode: Int, e: KeyEvent?): Boolean { - return handleVirtualKeys(keyCode, e, false) - } - - override fun readControlKey(): Boolean { - val extraKeysView = termSessionData?.extraKeysView - return (extraKeysView != null && extraKeysView.readControlButton()) || mVirtualControlKeyDown - } - - override fun readAltKey(): Boolean { - val extraKeysView = termSessionData?.extraKeysView - return (extraKeysView != null && extraKeysView.readAltButton()) || mVirtualFnKeyDown - } - - override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean { - if (mVirtualFnKeyDown) { - var resultingKeyCode: Int = -1 - var resultingCodePoint: Int = -1 - var altDown = false - val lowerCase = Character.toLowerCase(codePoint) - when (lowerCase.toChar()) { - // Arrow keys. - 'w' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP - 'a' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT - 's' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN - 'd' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT - - // Page up and down. - 'p' -> resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP - 'n' -> resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN - - // Some special keys: - 't' -> resultingKeyCode = KeyEvent.KEYCODE_TAB - 'i' -> resultingKeyCode = KeyEvent.KEYCODE_INSERT - 'h' -> resultingCodePoint = '~'.toInt() - - // Special characters to input. - 'u' -> resultingCodePoint = '_'.toInt() - 'l' -> resultingCodePoint = '|'.toInt() - - // Function keys. - '1', '2', '3', '4', '5', '6', '7', '8', '9' -> resultingKeyCode = codePoint - '1'.toInt() + KeyEvent.KEYCODE_F1 - '0' -> resultingKeyCode = KeyEvent.KEYCODE_F10 - - // Other special keys. - 'e' -> resultingCodePoint = 27 /*Escape*/ - '.' -> resultingCodePoint = 28 /*^.*/ - - 'b' // alt+b, jumping backward in readline. - , 'f' // alf+f, jumping forward in readline. - , 'x' // alt+x, common in emacs. - -> { - resultingCodePoint = lowerCase - altDown = true - } - - // Volume control. - 'v' -> { - resultingCodePoint = -1 - val audio = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI) - } - } - - if (resultingKeyCode != -1) { - if (session != null) { - val term = session.emulator - session.write(KeyHandler.getCode(resultingKeyCode, 0, term.isCursorKeysApplicationMode, term.isKeypadApplicationMode)) - } - } else if (resultingCodePoint != -1) { - session?.writeCodePoint(altDown, resultingCodePoint) - } - return true + } + KeyEvent.KEYCODE_BACK -> { + if (e?.action == KeyEvent.ACTION_DOWN) { + return termUI?.requireFinishAutoCompletion() ?: false } return false + } } - override fun onLongPress(event: MotionEvent?): Boolean { - // TODO + // TODO 自定义快捷键 + if (e != null && e.isCtrlPressed && e.isShiftPressed) { + // Get the unmodified code point: + val unicodeChar = e.getUnicodeChar(0).toChar() + + when (unicodeChar) { + 'v' -> termUI?.requirePaste() + 'n' -> termUI?.requireCreateNew() + 'z' -> termUI?.requireSwitchToPrevious() + 'x' -> termUI?.requireSwitchToNext() + 'f' -> termUI?.requireToggleFullScreen() + '-' -> changeFontSize(false) + '+' -> changeFontSize(true) + } + + // 当要触发 NeoTerm 快捷键时,屏蔽所有终端处理key + return true + } else if (e != null && e.isAltPressed) { + // Get the unmodified code point: + val unicodeChar = e.getUnicodeChar(0).toChar() + if (unicodeChar !in ('1'..'9')) { return false - } + } - private fun handleVirtualKeys(keyCode: Int, event: KeyEvent?, down: Boolean): Boolean { - if (event == null) { - return false + // Use Alt + num to switch sessions + val sessionIndex = unicodeChar.toInt() - '0'.toInt() + termUI?.requireSwitchTo(sessionIndex) + + // 当要触发 NeoTerm 快捷键时,屏蔽所有终端处理key + return true + } + return false + } + + override fun onKeyUp(keyCode: Int, e: KeyEvent?): Boolean { + return handleVirtualKeys(keyCode, e, false) + } + + override fun readControlKey(): Boolean { + val extraKeysView = termSessionData?.extraKeysView + return (extraKeysView != null && extraKeysView.readControlButton()) || mVirtualControlKeyDown + } + + override fun readAltKey(): Boolean { + val extraKeysView = termSessionData?.extraKeysView + return (extraKeysView != null && extraKeysView.readAltButton()) || mVirtualFnKeyDown + } + + override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean { + if (mVirtualFnKeyDown) { + var resultingKeyCode: Int = -1 + var resultingCodePoint: Int = -1 + var altDown = false + val lowerCase = Character.toLowerCase(codePoint) + when (lowerCase.toChar()) { + // Arrow keys. + 'w' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP + 'a' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT + 's' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN + 'd' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT + + // Page up and down. + 'p' -> resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP + 'n' -> resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN + + // Some special keys: + 't' -> resultingKeyCode = KeyEvent.KEYCODE_TAB + 'i' -> resultingKeyCode = KeyEvent.KEYCODE_INSERT + 'h' -> resultingCodePoint = '~'.toInt() + + // Special characters to input. + 'u' -> resultingCodePoint = '_'.toInt() + 'l' -> resultingCodePoint = '|'.toInt() + + // Function keys. + '1', '2', '3', '4', '5', '6', '7', '8', '9' -> resultingKeyCode = codePoint - '1'.toInt() + KeyEvent.KEYCODE_F1 + '0' -> resultingKeyCode = KeyEvent.KEYCODE_F10 + + // Other special keys. + 'e' -> resultingCodePoint = 27 /*Escape*/ + '.' -> resultingCodePoint = 28 /*^.*/ + + 'b' // alt+b, jumping backward in readline. + , 'f' // alf+f, jumping forward in readline. + , 'x' // alt+x, common in emacs. + -> { + resultingCodePoint = lowerCase + altDown = true } - val shellSession = termSessionData?.termSession as ShellTermSession? ?: return false - - // Volume keys as special keys - val volumeAsSpecialKeys = shellSession.shellProfile.enableSpecialVolumeKeys - - val inputDevice = event.device - if (inputDevice != null && inputDevice.keyboardType == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { - return false - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { - mVirtualControlKeyDown = down && volumeAsSpecialKeys - return true - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { - mVirtualFnKeyDown = down && volumeAsSpecialKeys - return true + // Volume control. + 'v' -> { + resultingCodePoint = -1 + val audio = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + audio.adjustSuggestedStreamVolume( + AudioManager.ADJUST_SAME, + AudioManager.USE_DEFAULT_STREAM_TYPE, + AudioManager.FLAG_SHOW_UI + ) } - return false + } + + if (resultingKeyCode != -1) { + if (session != null) { + val term = session.emulator + session.write( + KeyHandler.getCode( + resultingKeyCode, + 0, + term.isCursorKeysApplicationMode, + term.isKeypadApplicationMode + ) + ) + } + } else if (resultingCodePoint != -1) { + session?.writeCodePoint(altDown, resultingCodePoint) + } + return true + } + return false + } + + override fun onLongPress(event: MotionEvent?): Boolean { + // TODO + return false + } + + private fun handleVirtualKeys(keyCode: Int, event: KeyEvent?, down: Boolean): Boolean { + if (event == null) { + return false } - fun updateExtraKeys(title: String?, force: Boolean = false) { - val extraKeysView = termSessionData?.extraKeysView + val shellSession = termSessionData?.termSession as ShellTermSession? ?: return false - if (extraKeysView == null || title == null || title.isEmpty()) { - return - } + // Volume keys as special keys + val volumeAsSpecialKeys = shellSession.shellProfile.enableSpecialVolumeKeys - if ((lastTitle != title || force) - && updateExtraKeysVisibility()) { - removeExtraKeys() - ComponentManager.getComponent().showShortcutKeys(title, extraKeysView) - extraKeysView.updateButtons() - lastTitle = title - } + val inputDevice = event.device + if (inputDevice != null && inputDevice.keyboardType == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { + return false + } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + mVirtualControlKeyDown = down && volumeAsSpecialKeys + return true + } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + mVirtualFnKeyDown = down && volumeAsSpecialKeys + return true + } + return false + } + + fun updateExtraKeys(title: String?, force: Boolean = false) { + val extraKeysView = termSessionData?.extraKeysView + + if (extraKeysView == null || title == null || title.isEmpty()) { + return } - private fun updateExtraKeysVisibility(): Boolean { - val extraKeysView = termSessionData?.extraKeysView ?: return false - val shellSession = termSessionData?.termSession as ShellTermSession? ?: return false - - return if (shellSession.shellProfile.enableExtraKeys) { - extraKeysView.visibility = View.VISIBLE - true - } else { - extraKeysView.visibility = View.GONE - false - } + if ((lastTitle != title || force) + && updateExtraKeysVisibility() + ) { + removeExtraKeys() + ComponentManager.getComponent().showShortcutKeys(title, extraKeysView) + extraKeysView.updateButtons() + lastTitle = title } + } - private fun removeExtraKeys() { - val extraKeysView = termSessionData?.extraKeysView - extraKeysView?.clearUserKeys() - } + private fun updateExtraKeysVisibility(): Boolean { + val extraKeysView = termSessionData?.extraKeysView ?: return false + val shellSession = termSessionData?.termSession as ShellTermSession? ?: return false - private fun changeFontSize(increase: Boolean) { - val termView = termSessionData?.termView - if (termView != null) { - val changedSize = (if (increase) 1 else -1) * 2 - val fontSize = NeoPreference.validateFontSize(termView.textSize + changedSize) - termView.textSize = fontSize - NeoPreference.store(NeoPreference.KEY_FONT_SIZE, fontSize) - } + return if (shellSession.shellProfile.enableExtraKeys) { + extraKeysView.visibility = View.VISIBLE + true + } else { + extraKeysView.visibility = View.GONE + false } + } + + private fun removeExtraKeys() { + val extraKeysView = termSessionData?.extraKeysView + extraKeysView?.clearUserKeys() + } + + private fun changeFontSize(increase: Boolean) { + val termView = termSessionData?.termView + if (termView != null) { + val changedSize = (if (increase) 1 else -1) * 2 + val fontSize = NeoPreference.validateFontSize(termView.textSize + changedSize) + termView.textSize = fontSize + NeoPreference.store(NeoPreference.KEY_FONT_SIZE, fontSize) + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/session/xorg/XSession.kt b/app/src/main/java/io/neoterm/frontend/session/xorg/XSession.kt index 37616fa..3794dcf 100644 --- a/app/src/main/java/io/neoterm/frontend/session/xorg/XSession.kt +++ b/app/src/main/java/io/neoterm/frontend/session/xorg/XSession.kt @@ -1,6 +1,5 @@ package io.neoterm.frontend.session.xorg -import androidx.appcompat.app.AppCompatActivity import android.app.UiModeManager import android.content.Context import android.content.pm.ActivityInfo @@ -14,6 +13,7 @@ import android.view.* import android.view.inputmethod.InputMethodManager import android.widget.EditText import android.widget.FrameLayout +import androidx.appcompat.app.AppCompatActivity import io.neoterm.* import io.neoterm.frontend.session.xorg.client.XSessionData import io.neoterm.xorg.NeoXorgViewClient @@ -24,351 +24,398 @@ import java.util.* * @author kiva */ -class XSession constructor(private val mActivity: AppCompatActivity, val mSessionData: XSessionData) : NeoXorgViewClient { - var mSessionName = ""; +class XSession constructor(private val mActivity: AppCompatActivity, val mSessionData: XSessionData) : + NeoXorgViewClient { + var mSessionName = ""; - init { - if (Globals.InhibitSuspend) { - window.setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, - WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } - - mSessionData.client = this - NeoXorgSettings.init(this) - if (mSessionData.audioThread == null) { - mSessionData.audioThread = NeoAudioThread(this) - } + init { + if (Globals.InhibitSuspend) { + window.setFlags( + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + ) } - fun onPause() { - mSessionData.isPaused = true - if (mSessionData.glView != null) { - mSessionData.glView?.onPause() - } + mSessionData.client = this + NeoXorgSettings.init(this) + if (mSessionData.audioThread == null) { + mSessionData.audioThread = NeoAudioThread(this) } + } - fun onDestroy() { - if (mSessionData.glView != null) { - mSessionData.glView?.exitApp() - } + fun onPause() { + mSessionData.isPaused = true + if (mSessionData.glView != null) { + mSessionData.glView?.onPause() } + } - fun onResume() { - if (mSessionData.glView != null) { - mSessionData.glView?.onResume() - } - mSessionData.isPaused = false + fun onDestroy() { + if (mSessionData.glView != null) { + mSessionData.glView?.exitApp() } + } - override fun getContext() = mActivity + fun onResume() { + if (mSessionData.glView != null) { + mSessionData.glView?.onResume() + } + mSessionData.isPaused = false + } - override fun isKeyboardWithoutTextInputShown() = mSessionData.keyboardWithoutTextInputShown + override fun getContext() = mActivity - override fun isPaused() = mSessionData.isPaused + override fun isKeyboardWithoutTextInputShown() = mSessionData.keyboardWithoutTextInputShown - override fun runOnUiThread(runnable: Runnable?) = mActivity.runOnUiThread(runnable) + override fun isPaused() = mSessionData.isPaused - override fun getGLView() = mSessionData.glView + override fun runOnUiThread(runnable: Runnable?) = mActivity.runOnUiThread(runnable) - override fun getWindow() = mActivity.window!! + override fun getGLView() = mSessionData.glView - override fun getWindowManager() = mActivity.windowManager!! + override fun getWindow() = mActivity.window!! - override fun showScreenKeyboardWithoutTextInputField(keyboard: Int) { - val inputManager = mActivity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + override fun getWindowManager() = mActivity.windowManager!! - if (!isKeyboardWithoutTextInputShown) { - mSessionData.keyboardWithoutTextInputShown = true - runOnUiThread(Runnable { - if (keyboard == 0) { - inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) - inputManager.showSoftInput(glView, InputMethodManager.SHOW_FORCED) - window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) - } else { - if (mSessionData.screenKeyboard != null) - return@Runnable + override fun showScreenKeyboardWithoutTextInputField(keyboard: Int) { + val inputManager = mActivity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - val builtinKeyboard = BuiltInKeyboardView(mActivity, null) - builtinKeyboard.alpha = 0.7f - builtinKeyboard.changeKeyboard(keyboard) - builtinKeyboard.setOnKeyboardActionListener(object : KeyboardView.OnKeyboardActionListener { - override fun onPress(key: Int) { - var key = key - if (key == KeyEvent.KEYCODE_BACK) - return - if (key < 0) - return - for (k in builtinKeyboard.keyboard.keys) { - if (k.sticky && key == k.codes[0]) - return - } - if (key > 100000) { - key -= 100000 - mActivity.onKeyDown(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT)) - } - mActivity.onKeyDown(key, KeyEvent(KeyEvent.ACTION_DOWN, key)) - } - - override fun onRelease(key: Int) { - var key = key - if (key == KeyEvent.KEYCODE_BACK) { - builtinKeyboard.setOnKeyboardActionListener(null) - showScreenKeyboardWithoutTextInputField(0) // Hide keyboard - return - } - if (key == Keyboard.KEYCODE_SHIFT) { - builtinKeyboard.shift = !builtinKeyboard.shift - if (builtinKeyboard.shift && !builtinKeyboard.alt) - mActivity.onKeyDown(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT)) - else - mActivity.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT)) - builtinKeyboard.changeKeyboard(keyboard) - return - } - if (key == Keyboard.KEYCODE_ALT) { - builtinKeyboard.alt = !builtinKeyboard.alt - if (builtinKeyboard.alt) - mActivity.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT)) - else - builtinKeyboard.shift = false - builtinKeyboard.changeKeyboard(keyboard) - return - } - if (key < 0) - return - for (k in builtinKeyboard.keyboard.keys) { - if (k.sticky && key == k.codes[0]) { - if (k.on) { - builtinKeyboard.stickyKeys.add(key) - mActivity.onKeyDown(key, KeyEvent(KeyEvent.ACTION_DOWN, key)) - } else { - builtinKeyboard.stickyKeys.remove(key) - mActivity.onKeyUp(key, KeyEvent(KeyEvent.ACTION_UP, key)) - } - return - } - } - - var shifted = false - if (key > 100000) { - key -= 100000 - shifted = true - } - - mActivity.onKeyUp(key, KeyEvent(KeyEvent.ACTION_UP, key)) - - if (shifted) { - mActivity.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT)) - builtinKeyboard.stickyKeys.remove(KeyEvent.KEYCODE_SHIFT_LEFT) - for (k in builtinKeyboard.keyboard.keys) { - if (k.sticky && k.codes[0] == KeyEvent.KEYCODE_SHIFT_LEFT && k.on) { - k.on = false - builtinKeyboard.invalidateAllKeys() - } - } - } - } - - override fun onText(p1: CharSequence) {} - - override fun swipeLeft() {} - - override fun swipeRight() {} - - override fun swipeDown() {} - - override fun swipeUp() {} - - override fun onKey(p1: Int, p2: IntArray) {} - }) - mSessionData.screenKeyboard = builtinKeyboard - val layout = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM) - mSessionData.videoLayout!!.addView(mSessionData.screenKeyboard, layout) - } - }) + if (!isKeyboardWithoutTextInputShown) { + mSessionData.keyboardWithoutTextInputShown = true + runOnUiThread(Runnable { + if (keyboard == 0) { + inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) + inputManager.showSoftInput(glView, InputMethodManager.SHOW_FORCED) + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) } else { - mSessionData.keyboardWithoutTextInputShown = false - runOnUiThread { - if (mSessionData.screenKeyboard != null) { - mSessionData.videoLayout!!.removeView(mSessionData.screenKeyboard) - mSessionData.screenKeyboard = null + if (mSessionData.screenKeyboard != null) + return@Runnable + + val builtinKeyboard = BuiltInKeyboardView(mActivity, null) + builtinKeyboard.alpha = 0.7f + builtinKeyboard.changeKeyboard(keyboard) + builtinKeyboard.setOnKeyboardActionListener(object : KeyboardView.OnKeyboardActionListener { + override fun onPress(key: Int) { + var key = key + if (key == KeyEvent.KEYCODE_BACK) + return + if (key < 0) + return + for (k in builtinKeyboard.keyboard.keys) { + if (k.sticky && key == k.codes[0]) + return + } + if (key > 100000) { + key -= 100000 + mActivity.onKeyDown( + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT) + ) + } + mActivity.onKeyDown(key, KeyEvent(KeyEvent.ACTION_DOWN, key)) + } + + override fun onRelease(key: Int) { + var key = key + if (key == KeyEvent.KEYCODE_BACK) { + builtinKeyboard.setOnKeyboardActionListener(null) + showScreenKeyboardWithoutTextInputField(0) // Hide keyboard + return + } + if (key == Keyboard.KEYCODE_SHIFT) { + builtinKeyboard.shift = !builtinKeyboard.shift + if (builtinKeyboard.shift && !builtinKeyboard.alt) + mActivity.onKeyDown( + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT) + ) + else + mActivity.onKeyUp( + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT) + ) + builtinKeyboard.changeKeyboard(keyboard) + return + } + if (key == Keyboard.KEYCODE_ALT) { + builtinKeyboard.alt = !builtinKeyboard.alt + if (builtinKeyboard.alt) + mActivity.onKeyUp( + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT) + ) + else + builtinKeyboard.shift = false + builtinKeyboard.changeKeyboard(keyboard) + return + } + if (key < 0) + return + for (k in builtinKeyboard.keyboard.keys) { + if (k.sticky && key == k.codes[0]) { + if (k.on) { + builtinKeyboard.stickyKeys.add(key) + mActivity.onKeyDown(key, KeyEvent(KeyEvent.ACTION_DOWN, key)) + } else { + builtinKeyboard.stickyKeys.remove(key) + mActivity.onKeyUp(key, KeyEvent(KeyEvent.ACTION_UP, key)) + } + return } - window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) - inputManager.hideSoftInputFromWindow(glView!!.windowToken, 0) + } + + var shifted = false + if (key > 100000) { + key -= 100000 + shifted = true + } + + mActivity.onKeyUp(key, KeyEvent(KeyEvent.ACTION_UP, key)) + + if (shifted) { + mActivity.onKeyUp( + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT) + ) + builtinKeyboard.stickyKeys.remove(KeyEvent.KEYCODE_SHIFT_LEFT) + for (k in builtinKeyboard.keyboard.keys) { + if (k.sticky && k.codes[0] == KeyEvent.KEYCODE_SHIFT_LEFT && k.on) { + k.on = false + builtinKeyboard.invalidateAllKeys() + } + } + } } + + override fun onText(p1: CharSequence) {} + + override fun swipeLeft() {} + + override fun swipeRight() {} + + override fun swipeDown() {} + + override fun swipeUp() {} + + override fun onKey(p1: Int, p2: IntArray) {} + }) + mSessionData.screenKeyboard = builtinKeyboard + val layout = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT, + Gravity.BOTTOM + ) + mSessionData.videoLayout!!.addView(mSessionData.screenKeyboard, layout) } - glView!!.callNativeScreenKeyboardShown(if (isKeyboardWithoutTextInputShown) 1 else 0) + }) + } else { + mSessionData.keyboardWithoutTextInputShown = false + runOnUiThread { + if (mSessionData.screenKeyboard != null) { + mSessionData.videoLayout!!.removeView(mSessionData.screenKeyboard) + mSessionData.screenKeyboard = null + } + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) + inputManager.hideSoftInputFromWindow(glView!!.windowToken, 0) + } } + glView!!.callNativeScreenKeyboardShown(if (isKeyboardWithoutTextInputShown) 1 else 0) + } - override fun setScreenKeyboardHintMessage(hideMessage: String?) { - mSessionData.screenKeyboardHintMessage = hideMessage - if (mSessionData.screenKeyboard is EditText) { - runOnUiThread { - val editText = mSessionData.screenKeyboard as EditText? - editText?.hint = hideMessage ?: mActivity.getString(R.string.text_edit_click_here) - } - } + override fun setScreenKeyboardHintMessage(hideMessage: String?) { + mSessionData.screenKeyboardHintMessage = hideMessage + if (mSessionData.screenKeyboard is EditText) { + runOnUiThread { + val editText = mSessionData.screenKeyboard as EditText? + editText?.hint = hideMessage ?: mActivity.getString(R.string.text_edit_click_here) + } } + } - override fun isScreenKeyboardShown() = mSessionData.screenKeyboard != null + override fun isScreenKeyboardShown() = mSessionData.screenKeyboard != null - override fun showScreenKeyboard(oldText: String?) { - if (Globals.CompatibilityHacksTextInputEmulatesHwKeyboard) { - showScreenKeyboardWithoutTextInputField(Globals.TextInputKeyboard) - return - } - if (mSessionData.screenKeyboard != null) - return + override fun showScreenKeyboard(oldText: String?) { + if (Globals.CompatibilityHacksTextInputEmulatesHwKeyboard) { + showScreenKeyboardWithoutTextInputField(Globals.TextInputKeyboard) + return + } + if (mSessionData.screenKeyboard != null) + return - val screenKeyboard = EditText(mActivity, null, - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) - android.R.style.TextAppearance_Material_Widget_EditText - else android.R.style.TextAppearance_Widget_EditText) + val screenKeyboard = EditText( + mActivity, null, + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) + android.R.style.TextAppearance_Material_Widget_EditText + else android.R.style.TextAppearance_Widget_EditText + ) - val hint = mSessionData.screenKeyboardHintMessage - screenKeyboard.hint = hint ?: mActivity.getString(R.string.text_edit_click_here) - screenKeyboard.setText(oldText) + val hint = mSessionData.screenKeyboardHintMessage + screenKeyboard.hint = hint ?: mActivity.getString(R.string.text_edit_click_here) + screenKeyboard.setText(oldText) + screenKeyboard.setSelection(screenKeyboard.text.length) + screenKeyboard.setOnKeyListener(SimpleKeyListener(this)) + screenKeyboard.setBackgroundColor(mActivity.resources.getColor(android.R.color.primary_text_light)) + screenKeyboard.setTextColor(mActivity.resources.getColor(android.R.color.background_light)) + + if (isRunningOnOUYA && Globals.TvBorders) + screenKeyboard.setPadding(100, 100, 100, 100) // Bad bad HDMI TVs all have cropped borders + mSessionData.screenKeyboard = screenKeyboard + mSessionData.videoLayout!!.addView(mSessionData.screenKeyboard) + + screenKeyboard.inputType = InputType.TYPE_CLASS_TEXT + screenKeyboard.isFocusableInTouchMode = true + screenKeyboard.isFocusable = true + screenKeyboard.postDelayed({ + screenKeyboard.requestFocus() + screenKeyboard.dispatchTouchEvent( + MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_DOWN, + 0f, + 0f, + 0 + ) + ) + screenKeyboard.dispatchTouchEvent( + MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_UP, + 0f, + 0f, + 0 + ) + ) + screenKeyboard.postDelayed({ + screenKeyboard.requestFocus() screenKeyboard.setSelection(screenKeyboard.text.length) - screenKeyboard.setOnKeyListener(SimpleKeyListener(this)) - screenKeyboard.setBackgroundColor(mActivity.resources.getColor(android.R.color.primary_text_light)) - screenKeyboard.setTextColor(mActivity.resources.getColor(android.R.color.background_light)) + }, 100) + }, 300) + } - if (isRunningOnOUYA && Globals.TvBorders) - screenKeyboard.setPadding(100, 100, 100, 100) // Bad bad HDMI TVs all have cropped borders - mSessionData.screenKeyboard = screenKeyboard - mSessionData.videoLayout!!.addView(mSessionData.screenKeyboard) + override fun hideScreenKeyboard() { + val inputManager = mActivity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - screenKeyboard.inputType = InputType.TYPE_CLASS_TEXT - screenKeyboard.isFocusableInTouchMode = true - screenKeyboard.isFocusable = true - screenKeyboard.postDelayed({ - screenKeyboard.requestFocus() - screenKeyboard.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 0f, 0f, 0)) - screenKeyboard.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 0f, 0f, 0)) - screenKeyboard.postDelayed({ - screenKeyboard.requestFocus() - screenKeyboard.setSelection(screenKeyboard.text.length) - }, 100) - }, 300) + if (isKeyboardWithoutTextInputShown) + showScreenKeyboardWithoutTextInputField(Globals.TextInputKeyboard) + + if (mSessionData.screenKeyboard == null || mSessionData.screenKeyboard !is EditText) + return + + synchronized(mSessionData.textInput) { + val text = (mSessionData.screenKeyboard as EditText).text.toString() + for (i in 0 until text.length) { + NeoRenderer.callNativeTextInput(text[i].toInt(), text.codePointAt(i)) + } + } + NeoRenderer.callNativeTextInputFinished() + inputManager.hideSoftInputFromWindow(mSessionData.screenKeyboard!!.windowToken, 0) + mSessionData.videoLayout!!.removeView(mSessionData.screenKeyboard) + mSessionData.screenKeyboard = null + glView!!.isFocusableInTouchMode = true + glView!!.isFocusable = true + glView!!.requestFocus() + } + + override fun updateScreenOrientation() { + var rotation: Int = windowManager.defaultDisplay.rotation + NeoAccelerometerReader.setGyroInvertedOrientation( + rotation == Surface.ROTATION_180 || rotation == Surface.ROTATION_270 + ) + } + + override fun initScreenOrientation() { + Globals.AutoDetectOrientation = true + if (Globals.AutoDetectOrientation) { + mActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_USER + return + } + mActivity.requestedOrientation = + if (Globals.HorizontalOrientation) ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE + else ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT + } + + override fun isRunningOnOUYA(): Boolean { + try { + mActivity.packageManager.getPackageInfo("tv.ouya", 0) + return true + } catch (e: PackageManager.NameNotFoundException) { } - override fun hideScreenKeyboard() { - val inputManager = mActivity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + val uiModeManager = mActivity.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager? + return uiModeManager?.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION || Globals.OuyaEmulation + } - if (isKeyboardWithoutTextInputShown) - showScreenKeyboardWithoutTextInputField(Globals.TextInputKeyboard) + override fun setSystemMousePointerVisible(visible: Int) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + glView?.pointerIcon = android.view.PointerIcon.getSystemIcon( + mActivity, + if (visible == 0) android.view.PointerIcon.TYPE_NULL + else android.view.PointerIcon.TYPE_DEFAULT + ) + } + } - if (mSessionData.screenKeyboard == null || mSessionData.screenKeyboard !is EditText) - return + class SimpleKeyListener(var client: NeoXorgViewClient) : View.OnKeyListener { - synchronized(mSessionData.textInput) { - val text = (mSessionData.screenKeyboard as EditText).text.toString() - for (i in 0 until text.length) { - NeoRenderer.callNativeTextInput(text[i].toInt(), text.codePointAt(i)) - } - } - NeoRenderer.callNativeTextInputFinished() - inputManager.hideSoftInputFromWindow(mSessionData.screenKeyboard!!.windowToken, 0) - mSessionData.videoLayout!!.removeView(mSessionData.screenKeyboard) - mSessionData.screenKeyboard = null - glView!!.isFocusableInTouchMode = true - glView!!.isFocusable = true - glView!!.requestFocus() + override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean { + if (event.action == KeyEvent.ACTION_UP && (keyCode == KeyEvent.KEYCODE_ENTER || + keyCode == KeyEvent.KEYCODE_BACK || + keyCode == KeyEvent.KEYCODE_MENU || + keyCode == KeyEvent.KEYCODE_BUTTON_A || + keyCode == KeyEvent.KEYCODE_BUTTON_B || + keyCode == KeyEvent.KEYCODE_BUTTON_X || + keyCode == KeyEvent.KEYCODE_BUTTON_Y || + keyCode == KeyEvent.KEYCODE_BUTTON_1 || + keyCode == KeyEvent.KEYCODE_BUTTON_2 || + keyCode == KeyEvent.KEYCODE_BUTTON_3 || + keyCode == KeyEvent.KEYCODE_BUTTON_4) + ) { + client.hideScreenKeyboard() + return true + } + return false + } + } + + class BuiltInKeyboardView(context: Context, attrs: android.util.AttributeSet?) : KeyboardView(context, attrs) { + var shift = false + var alt = false + var stickyKeys = TreeSet() + + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { + if (ev.y < top) + return false + if (ev.action == MotionEvent.ACTION_DOWN || ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_MOVE) { + // Convert pointer coords, this will lose multitiouch data, however KeyboardView does not support multitouch anyway + val converted = + MotionEvent.obtain(ev.downTime, ev.eventTime, ev.action, ev.x, ev.y - top.toFloat(), ev.metaState) + return super.dispatchTouchEvent(converted) + } + return false } - override fun updateScreenOrientation() { - var rotation: Int = windowManager.defaultDisplay.rotation - NeoAccelerometerReader.setGyroInvertedOrientation( - rotation == Surface.ROTATION_180 || rotation == Surface.ROTATION_270) + override fun onKeyDown(key: Int, event: KeyEvent): Boolean { + return false } - override fun initScreenOrientation() { - Globals.AutoDetectOrientation = true - if (Globals.AutoDetectOrientation) { - mActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_USER - return - } - mActivity.requestedOrientation = - if (Globals.HorizontalOrientation) ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE - else ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT + override fun onKeyUp(key: Int, event: KeyEvent): Boolean { + return false } - override fun isRunningOnOUYA(): Boolean { - try { - mActivity.packageManager.getPackageInfo("tv.ouya", 0) - return true - } catch (e: PackageManager.NameNotFoundException) { - } - - val uiModeManager = mActivity.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager? - return uiModeManager?.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION || Globals.OuyaEmulation - } - - override fun setSystemMousePointerVisible(visible: Int) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - glView?.pointerIcon = android.view.PointerIcon.getSystemIcon(mActivity, - if (visible == 0) android.view.PointerIcon.TYPE_NULL - else android.view.PointerIcon.TYPE_DEFAULT) - } - } - - class SimpleKeyListener(var client: NeoXorgViewClient) : View.OnKeyListener { - - override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean { - if (event.action == KeyEvent.ACTION_UP && (keyCode == KeyEvent.KEYCODE_ENTER || - keyCode == KeyEvent.KEYCODE_BACK || - keyCode == KeyEvent.KEYCODE_MENU || - keyCode == KeyEvent.KEYCODE_BUTTON_A || - keyCode == KeyEvent.KEYCODE_BUTTON_B || - keyCode == KeyEvent.KEYCODE_BUTTON_X || - keyCode == KeyEvent.KEYCODE_BUTTON_Y || - keyCode == KeyEvent.KEYCODE_BUTTON_1 || - keyCode == KeyEvent.KEYCODE_BUTTON_2 || - keyCode == KeyEvent.KEYCODE_BUTTON_3 || - keyCode == KeyEvent.KEYCODE_BUTTON_4)) { - client.hideScreenKeyboard() - return true - } - return false - } - } - - class BuiltInKeyboardView(context: Context, attrs: android.util.AttributeSet?) : KeyboardView(context, attrs) { - var shift = false - var alt = false - var stickyKeys = TreeSet() - - override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - if (ev.y < top) - return false - if (ev.action == MotionEvent.ACTION_DOWN || ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_MOVE) { - // Convert pointer coords, this will lose multitiouch data, however KeyboardView does not support multitouch anyway - val converted = MotionEvent.obtain(ev.downTime, ev.eventTime, ev.action, ev.x, ev.y - top.toFloat(), ev.metaState) - return super.dispatchTouchEvent(converted) - } - return false - } - - override fun onKeyDown(key: Int, event: KeyEvent): Boolean { - return false - } - - override fun onKeyUp(key: Int, event: KeyEvent): Boolean { - return false - } - - fun changeKeyboard(keyboardIndex: Int) { - val idx = (if (shift) 1 else 0) + if (alt) 2 else 0 - val keyboard = Keyboard(context, NeoTextInput.TextInputKeyboardList[idx][keyboardIndex]) - isPreviewEnabled = false - isProximityCorrectionEnabled = false - for (k in keyboard.keys) { - if (stickyKeys.contains(k.codes[0])) { - k.on = true - invalidateAllKeys() - } - } + fun changeKeyboard(keyboardIndex: Int) { + val idx = (if (shift) 1 else 0) + if (alt) 2 else 0 + val keyboard = Keyboard(context, NeoTextInput.TextInputKeyboardList[idx][keyboardIndex]) + isPreviewEnabled = false + isProximityCorrectionEnabled = false + for (k in keyboard.keys) { + if (stickyKeys.contains(k.codes[0])) { + k.on = true + invalidateAllKeys() } + } } + } } diff --git a/app/src/main/java/io/neoterm/frontend/session/xorg/client/XSessionData.kt b/app/src/main/java/io/neoterm/frontend/session/xorg/client/XSessionData.kt index 97c41d2..aa9cac2 100644 --- a/app/src/main/java/io/neoterm/frontend/session/xorg/client/XSessionData.kt +++ b/app/src/main/java/io/neoterm/frontend/session/xorg/client/XSessionData.kt @@ -11,15 +11,15 @@ import java.util.* * @author kiva */ class XSessionData { - var videoLayout: FrameLayout? = null - var audioThread: NeoAudioThread? = null - var screenKeyboard: View? = null - var glView: NeoGLView? = null + var videoLayout: FrameLayout? = null + var audioThread: NeoAudioThread? = null + var screenKeyboard: View? = null + var glView: NeoGLView? = null - var isPaused = false - var client: NeoXorgViewClient? = null + var isPaused = false + var client: NeoXorgViewClient? = null - var keyboardWithoutTextInputShown = false - var screenKeyboardHintMessage: String? = null - var textInput = LinkedList() + var keyboardWithoutTextInputShown = false + var screenKeyboardHintMessage: String? = null + var textInput = LinkedList() } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/terminal/GestureAndScaleRecognizer.kt b/app/src/main/java/io/neoterm/frontend/terminal/GestureAndScaleRecognizer.kt index ec435b2..6ed3b32 100755 --- a/app/src/main/java/io/neoterm/frontend/terminal/GestureAndScaleRecognizer.kt +++ b/app/src/main/java/io/neoterm/frontend/terminal/GestureAndScaleRecognizer.kt @@ -9,106 +9,106 @@ import android.view.ScaleGestureDetector /** A combination of [GestureDetector] and [ScaleGestureDetector]. */ internal class GestureAndScaleRecognizer(context: Context, val mListener: Listener) { - interface Listener { - fun onSingleTapUp(e: MotionEvent): Boolean + interface Listener { + fun onSingleTapUp(e: MotionEvent): Boolean - fun onDoubleTap(e: MotionEvent): Boolean + fun onDoubleTap(e: MotionEvent): Boolean + // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event + // e.g in vim, we can change window size with fingers moving. + fun onDoubleTapEvent(e: MotionEvent): Boolean + + fun onScroll(e2: MotionEvent, dx: Float, dy: Float): Boolean + + fun onFling(e: MotionEvent, velocityX: Float, velocityY: Float): Boolean + + fun onScale(focusX: Float, focusY: Float, scale: Float): Boolean + + fun onDown(x: Float, y: Float): Boolean + + fun onUp(e: MotionEvent): Boolean + + fun onLongPress(e: MotionEvent) + } + + private val mGestureDetector: GestureDetector + private val mScaleDetector: ScaleGestureDetector + var isAfterLongPress: Boolean = false + + init { + + mGestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { + override fun onScroll(e1: MotionEvent, e2: MotionEvent, dx: Float, dy: Float): Boolean { + return mListener.onScroll(e2, dx, dy) + } + + override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { + return mListener.onFling(e2, velocityX, velocityY) + } + + override fun onDown(e: MotionEvent): Boolean { + return mListener.onDown(e.x, e.y) + } + + override fun onLongPress(e: MotionEvent) { + mListener.onLongPress(e) + isAfterLongPress = true + } + }, null, true /* ignoreMultitouch */) + + mGestureDetector.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener { + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event // e.g in vim, we can change window size with fingers moving. - fun onDoubleTapEvent(e: MotionEvent): Boolean + mListener.onUp(e) - fun onScroll(e2: MotionEvent, dx: Float, dy: Float): Boolean + return mListener.onSingleTapUp(e) + } - fun onFling(e: MotionEvent, velocityX: Float, velocityY: Float): Boolean - - fun onScale(focusX: Float, focusY: Float, scale: Float): Boolean - - fun onDown(x: Float, y: Float): Boolean - - fun onUp(e: MotionEvent): Boolean - - fun onLongPress(e: MotionEvent) - } - - private val mGestureDetector: GestureDetector - private val mScaleDetector: ScaleGestureDetector - var isAfterLongPress: Boolean = false - - init { - - mGestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { - override fun onScroll(e1: MotionEvent, e2: MotionEvent, dx: Float, dy: Float): Boolean { - return mListener.onScroll(e2, dx, dy) - } - - override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { - return mListener.onFling(e2, velocityX, velocityY) - } - - override fun onDown(e: MotionEvent): Boolean { - return mListener.onDown(e.x, e.y) - } - - override fun onLongPress(e: MotionEvent) { - mListener.onLongPress(e) - isAfterLongPress = true - } - }, null, true /* ignoreMultitouch */) - - mGestureDetector.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener { - override fun onSingleTapConfirmed(e: MotionEvent): Boolean { - // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event - // e.g in vim, we can change window size with fingers moving. - mListener.onUp(e) - - return mListener.onSingleTapUp(e) - } - - override fun onDoubleTap(e: MotionEvent): Boolean { - return mListener.onDoubleTap(e) - } - - override fun onDoubleTapEvent(e: MotionEvent): Boolean { - // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event - // e.g in vim, we can change window size with fingers moving. - - // Disable triggering long press which will prevent further double tap motion from - // receiving, e.g. when you double tap and drag downwards slowly. - if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) { - when (e.action) { - MotionEvent.ACTION_DOWN -> - mGestureDetector.setIsLongpressEnabled(false) - MotionEvent.ACTION_UP -> - mGestureDetector.setIsLongpressEnabled(true) - } - return mListener.onDoubleTapEvent(e) - } - return false - } - }) - - mScaleDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() { - override fun onScaleBegin(detector: ScaleGestureDetector): Boolean { - return true - } - - override fun onScale(detector: ScaleGestureDetector): Boolean { - return mListener.onScale(detector.focusX, detector.focusY, detector.scaleFactor) - } - }) + override fun onDoubleTap(e: MotionEvent): Boolean { + return mListener.onDoubleTap(e) + } + override fun onDoubleTapEvent(e: MotionEvent): Boolean { // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event // e.g in vim, we can change window size with fingers moving. - mScaleDetector.isQuickScaleEnabled = false; - } - fun onTouchEvent(event: MotionEvent) { - mGestureDetector.onTouchEvent(event) - mScaleDetector.onTouchEvent(event) + // Disable triggering long press which will prevent further double tap motion from + // receiving, e.g. when you double tap and drag downwards slowly. + if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) { + when (e.action) { + MotionEvent.ACTION_DOWN -> + mGestureDetector.setIsLongpressEnabled(false) + MotionEvent.ACTION_UP -> + mGestureDetector.setIsLongpressEnabled(true) + } + return mListener.onDoubleTapEvent(e) + } + return false + } + }) - // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event - // e.g in vim, we can change window size with fingers moving. + mScaleDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() { + override fun onScaleBegin(detector: ScaleGestureDetector): Boolean { + return true + } + + override fun onScale(detector: ScaleGestureDetector): Boolean { + return mListener.onScale(detector.focusX, detector.focusY, detector.scaleFactor) + } + }) + + // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event + // e.g in vim, we can change window size with fingers moving. + mScaleDetector.isQuickScaleEnabled = false; + } + + fun onTouchEvent(event: MotionEvent) { + mGestureDetector.onTouchEvent(event) + mScaleDetector.onTouchEvent(event) + + // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event + // e.g in vim, we can change window size with fingers moving. // when (event.action) { // MotionEvent.ACTION_DOWN -> isAfterLongPress = false // MotionEvent.ACTION_UP -> if (!isAfterLongPress) { @@ -117,9 +117,9 @@ internal class GestureAndScaleRecognizer(context: Context, val mListener: Listen // mListener.onUp(event) // } // } - } + } - val isInProgress: Boolean - get() = mScaleDetector.isInProgress + val isInProgress: Boolean + get() = mScaleDetector.isInProgress } diff --git a/app/src/main/java/io/neoterm/frontend/terminal/TerminalRenderer.java b/app/src/main/java/io/neoterm/frontend/terminal/TerminalRenderer.java index 39b9563..a8ee1f5 100755 --- a/app/src/main/java/io/neoterm/frontend/terminal/TerminalRenderer.java +++ b/app/src/main/java/io/neoterm/frontend/terminal/TerminalRenderer.java @@ -4,12 +4,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.Typeface; - -import io.neoterm.backend.TerminalBuffer; -import io.neoterm.backend.TerminalEmulator; -import io.neoterm.backend.TerminalRow; -import io.neoterm.backend.TextStyle; -import io.neoterm.backend.WcWidth; +import io.neoterm.backend.*; /** * Renderer of a {@link TerminalEmulator} into a {@link Canvas}. @@ -18,227 +13,239 @@ import io.neoterm.backend.WcWidth; */ final class TerminalRenderer { - final int mTextSize; - final Typeface mTypeface; - private final Paint mTextPaint = new Paint(); + final int mTextSize; + final Typeface mTypeface; + private final Paint mTextPaint = new Paint(); - /** The width of a single mono spaced character obtained by {@link Paint#measureText(String)} on a single 'X'. */ - final float mFontWidth; - /** The {@link Paint#getFontSpacing()}. See http://www.fampennings.nl/maarten/android/08numgrid/font.png */ - final int mFontLineSpacing; - /** The {@link Paint#ascent()}. See http://www.fampennings.nl/maarten/android/08numgrid/font.png */ - private final int mFontAscent; - /** The {@link #mFontLineSpacing} + {@link #mFontAscent}. */ - final int mFontLineSpacingAndAscent; + /** + * The width of a single mono spaced character obtained by {@link Paint#measureText(String)} on a single 'X'. + */ + final float mFontWidth; + /** + * The {@link Paint#getFontSpacing()}. See http://www.fampennings.nl/maarten/android/08numgrid/font.png + */ + final int mFontLineSpacing; + /** + * The {@link Paint#ascent()}. See http://www.fampennings.nl/maarten/android/08numgrid/font.png + */ + private final int mFontAscent; + /** + * The {@link #mFontLineSpacing} + {@link #mFontAscent}. + */ + final int mFontLineSpacingAndAscent; - /** AutoCompletion PopupWindow need them to show popup window */ - protected float savedLastDrawnLineX; - protected float savedLastDrawnLineY; + /** + * AutoCompletion PopupWindow need them to show popup window + */ + protected float savedLastDrawnLineX; + protected float savedLastDrawnLineY; - private final float[] asciiMeasures = new float[127]; + private final float[] asciiMeasures = new float[127]; - public TerminalRenderer(int textSize, Typeface typeface) { - mTextSize = textSize; - mTypeface = typeface; + public TerminalRenderer(int textSize, Typeface typeface) { + mTextSize = textSize; + mTypeface = typeface; - mTextPaint.setTypeface(typeface); - mTextPaint.setAntiAlias(true); - mTextPaint.setTextSize(textSize); + mTextPaint.setTypeface(typeface); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize(textSize); - mFontLineSpacing = (int) Math.ceil(mTextPaint.getFontSpacing()); - mFontAscent = (int) Math.ceil(mTextPaint.ascent()); - mFontLineSpacingAndAscent = mFontLineSpacing + mFontAscent; - mFontWidth = mTextPaint.measureText("X"); + mFontLineSpacing = (int) Math.ceil(mTextPaint.getFontSpacing()); + mFontAscent = (int) Math.ceil(mTextPaint.ascent()); + mFontLineSpacingAndAscent = mFontLineSpacing + mFontAscent; + mFontWidth = mTextPaint.measureText("X"); - StringBuilder sb = new StringBuilder(" "); - for (int i = 0; i < asciiMeasures.length; i++) { - sb.setCharAt(0, (char) i); - asciiMeasures[i] = mTextPaint.measureText(sb, 0, 1); - } + StringBuilder sb = new StringBuilder(" "); + for (int i = 0; i < asciiMeasures.length; i++) { + sb.setCharAt(0, (char) i); + asciiMeasures[i] = mTextPaint.measureText(sb, 0, 1); } + } - /** Render the terminal to a canvas with at a specified row scroll, and an optional rectangular selection. */ - public final void render(TerminalEmulator mEmulator, Canvas canvas, int topRow, - int selectionY1, int selectionY2, int selectionX1, int selectionX2) { - final boolean reverseVideo = mEmulator.isReverseVideo(); - final int endRow = topRow + mEmulator.mRows; - final int columns = mEmulator.mColumns; - final int cursorCol = mEmulator.getCursorCol(); - final int cursorRow = mEmulator.getCursorRow(); - final boolean cursorVisible = mEmulator.isShowingCursor(); - final TerminalBuffer screen = mEmulator.getScreen(); - final int[] palette = mEmulator.mColors.mCurrentColors; - final int cursorShape = mEmulator.getCursorStyle(); + /** + * Render the terminal to a canvas with at a specified row scroll, and an optional rectangular selection. + */ + public final void render(TerminalEmulator mEmulator, Canvas canvas, int topRow, + int selectionY1, int selectionY2, int selectionX1, int selectionX2) { + final boolean reverseVideo = mEmulator.isReverseVideo(); + final int endRow = topRow + mEmulator.mRows; + final int columns = mEmulator.mColumns; + final int cursorCol = mEmulator.getCursorCol(); + final int cursorRow = mEmulator.getCursorRow(); + final boolean cursorVisible = mEmulator.isShowingCursor(); + final TerminalBuffer screen = mEmulator.getScreen(); + final int[] palette = mEmulator.mColors.mCurrentColors; + final int cursorShape = mEmulator.getCursorStyle(); - if (reverseVideo) - canvas.drawColor(palette[TextStyle.COLOR_INDEX_FOREGROUND], PorterDuff.Mode.SRC); + if (reverseVideo) + canvas.drawColor(palette[TextStyle.COLOR_INDEX_FOREGROUND], PorterDuff.Mode.SRC); - float heightOffset = mFontLineSpacingAndAscent; - for (int row = topRow; row < endRow; row++) { - heightOffset += mFontLineSpacing; + float heightOffset = mFontLineSpacingAndAscent; + for (int row = topRow; row < endRow; row++) { + heightOffset += mFontLineSpacing; - final int cursorX = (row == cursorRow && cursorVisible) ? cursorCol : -1; - int selx1 = -1, selx2 = -1; - if (row >= selectionY1 && row <= selectionY2) { - if (row == selectionY1) selx1 = selectionX1; - selx2 = (row == selectionY2) ? selectionX2 : mEmulator.mColumns; - } + final int cursorX = (row == cursorRow && cursorVisible) ? cursorCol : -1; + int selx1 = -1, selx2 = -1; + if (row >= selectionY1 && row <= selectionY2) { + if (row == selectionY1) selx1 = selectionX1; + selx2 = (row == selectionY2) ? selectionX2 : mEmulator.mColumns; + } - TerminalRow lineObject = screen.allocateFullLineIfNecessary(screen.externalToInternalRow(row)); - final char[] line = lineObject.mText; - final int charsUsedInLine = lineObject.getSpaceUsed(); + TerminalRow lineObject = screen.allocateFullLineIfNecessary(screen.externalToInternalRow(row)); + final char[] line = lineObject.mText; + final int charsUsedInLine = lineObject.getSpaceUsed(); - long lastRunStyle = 0; - boolean lastRunInsideCursor = false; - int lastRunStartColumn = -1; - int lastRunStartIndex = 0; - boolean lastRunFontWidthMismatch = false; - int currentCharIndex = 0; - float measuredWidthForRun = 0.f; + long lastRunStyle = 0; + boolean lastRunInsideCursor = false; + int lastRunStartColumn = -1; + int lastRunStartIndex = 0; + boolean lastRunFontWidthMismatch = false; + int currentCharIndex = 0; + float measuredWidthForRun = 0.f; - for (int column = 0; column < columns; ) { - final char charAtIndex = line[currentCharIndex]; - final boolean charIsHighsurrogate = Character.isHighSurrogate(charAtIndex); - final int charsForCodePoint = charIsHighsurrogate ? 2 : 1; - final int codePoint = charIsHighsurrogate ? Character.toCodePoint(charAtIndex, line[currentCharIndex + 1]) : charAtIndex; - final int codePointWcWidth = WcWidth.width(codePoint); - final boolean insideCursor = (column >= selx1 && column <= selx2) || (cursorX == column || (codePointWcWidth == 2 && cursorX == column + 1)); - final long style = lineObject.getStyle(column); + for (int column = 0; column < columns; ) { + final char charAtIndex = line[currentCharIndex]; + final boolean charIsHighsurrogate = Character.isHighSurrogate(charAtIndex); + final int charsForCodePoint = charIsHighsurrogate ? 2 : 1; + final int codePoint = charIsHighsurrogate ? Character.toCodePoint(charAtIndex, line[currentCharIndex + 1]) : charAtIndex; + final int codePointWcWidth = WcWidth.width(codePoint); + final boolean insideCursor = (column >= selx1 && column <= selx2) || (cursorX == column || (codePointWcWidth == 2 && cursorX == column + 1)); + final long style = lineObject.getStyle(column); - // Check if the measured text width for this code point is not the same as that expected by wcwidth(). - // This could happen for some fonts which are not truly monospace, or for more exotic characters such as - // smileys which android font renders as wide. - // If this is detected, we draw this code point scaled to match what wcwidth() expects. - final float measuredCodePointWidth = (codePoint < asciiMeasures.length) ? asciiMeasures[codePoint] : mTextPaint.measureText(line, - currentCharIndex, charsForCodePoint); - final boolean fontWidthMismatch = Math.abs(measuredCodePointWidth / mFontWidth - codePointWcWidth) > 0.01; + // Check if the measured text width for this code point is not the same as that expected by wcwidth(). + // This could happen for some fonts which are not truly monospace, or for more exotic characters such as + // smileys which android font renders as wide. + // If this is detected, we draw this code point scaled to match what wcwidth() expects. + final float measuredCodePointWidth = (codePoint < asciiMeasures.length) ? asciiMeasures[codePoint] : mTextPaint.measureText(line, + currentCharIndex, charsForCodePoint); + final boolean fontWidthMismatch = Math.abs(measuredCodePointWidth / mFontWidth - codePointWcWidth) > 0.01; - if (style != lastRunStyle || insideCursor != lastRunInsideCursor || fontWidthMismatch || lastRunFontWidthMismatch) { - if (column == 0) { - // Skip first column as there is nothing to draw, just record the current style. - } else { - final int columnWidthSinceLastRun = column - lastRunStartColumn; - final int charsSinceLastRun = currentCharIndex - lastRunStartIndex; - int cursorColor = lastRunInsideCursor ? mEmulator.mColors.mCurrentColors[TextStyle.COLOR_INDEX_CURSOR] : 0; - drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, - lastRunStartIndex, charsSinceLastRun, measuredWidthForRun, - cursorColor, cursorShape, lastRunStyle, reverseVideo); - } - measuredWidthForRun = 0.f; - lastRunStyle = style; - lastRunInsideCursor = insideCursor; - lastRunStartColumn = column; - lastRunStartIndex = currentCharIndex; - lastRunFontWidthMismatch = fontWidthMismatch; - } - measuredWidthForRun += measuredCodePointWidth; - column += codePointWcWidth; - currentCharIndex += charsForCodePoint; - while (currentCharIndex < charsUsedInLine && WcWidth.width(line, currentCharIndex) <= 0) { - // Eat combining chars so that they are treated as part of the last non-combining code point, - // instead of e.g. being considered inside the cursorColor in the next run. - currentCharIndex += Character.isHighSurrogate(line[currentCharIndex]) ? 2 : 1; - } - } - - final int columnWidthSinceLastRun = columns - lastRunStartColumn; + if (style != lastRunStyle || insideCursor != lastRunInsideCursor || fontWidthMismatch || lastRunFontWidthMismatch) { + if (column == 0) { + // Skip first column as there is nothing to draw, just record the current style. + } else { + final int columnWidthSinceLastRun = column - lastRunStartColumn; final int charsSinceLastRun = currentCharIndex - lastRunStartIndex; int cursorColor = lastRunInsideCursor ? mEmulator.mColors.mCurrentColors[TextStyle.COLOR_INDEX_CURSOR] : 0; - drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, lastRunStartIndex, charsSinceLastRun, - measuredWidthForRun, cursorColor, cursorShape, lastRunStyle, reverseVideo); + drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, + lastRunStartIndex, charsSinceLastRun, measuredWidthForRun, + cursorColor, cursorShape, lastRunStyle, reverseVideo); + } + measuredWidthForRun = 0.f; + lastRunStyle = style; + lastRunInsideCursor = insideCursor; + lastRunStartColumn = column; + lastRunStartIndex = currentCharIndex; + lastRunFontWidthMismatch = fontWidthMismatch; } + measuredWidthForRun += measuredCodePointWidth; + column += codePointWcWidth; + currentCharIndex += charsForCodePoint; + while (currentCharIndex < charsUsedInLine && WcWidth.width(line, currentCharIndex) <= 0) { + // Eat combining chars so that they are treated as part of the last non-combining code point, + // instead of e.g. being considered inside the cursorColor in the next run. + currentCharIndex += Character.isHighSurrogate(line[currentCharIndex]) ? 2 : 1; + } + } + + final int columnWidthSinceLastRun = columns - lastRunStartColumn; + final int charsSinceLastRun = currentCharIndex - lastRunStartIndex; + int cursorColor = lastRunInsideCursor ? mEmulator.mColors.mCurrentColors[TextStyle.COLOR_INDEX_CURSOR] : 0; + drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, lastRunStartIndex, charsSinceLastRun, + measuredWidthForRun, cursorColor, cursorShape, lastRunStyle, reverseVideo); + } + } + + private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int startColumn, int runWidthColumns, + int startCharIndex, int runWidthChars, float mes, int cursor, int cursorStyle, + long textStyle, boolean reverseVideo) { + int foreColor = TextStyle.decodeForeColor(textStyle); + final int effect = TextStyle.decodeEffect(textStyle); + int backColor = TextStyle.decodeBackColor(textStyle); + final boolean bold = (effect & (TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_BLINK)) != 0; + final boolean underline = (effect & TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE) != 0; + final boolean italic = (effect & TextStyle.CHARACTER_ATTRIBUTE_ITALIC) != 0; + final boolean strikeThrough = (effect & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0; + final boolean dim = (effect & TextStyle.CHARACTER_ATTRIBUTE_DIM) != 0; + + if ((foreColor & 0xff000000) != 0xff000000) { + // Let bold have bright colors if applicable (one of the first 8): + if (bold && foreColor >= 0 && foreColor < 8) foreColor += 8; + foreColor = palette[foreColor]; } - private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int startColumn, int runWidthColumns, - int startCharIndex, int runWidthChars, float mes, int cursor, int cursorStyle, - long textStyle, boolean reverseVideo) { - int foreColor = TextStyle.decodeForeColor(textStyle); - final int effect = TextStyle.decodeEffect(textStyle); - int backColor = TextStyle.decodeBackColor(textStyle); - final boolean bold = (effect & (TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_BLINK)) != 0; - final boolean underline = (effect & TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE) != 0; - final boolean italic = (effect & TextStyle.CHARACTER_ATTRIBUTE_ITALIC) != 0; - final boolean strikeThrough = (effect & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0; - final boolean dim = (effect & TextStyle.CHARACTER_ATTRIBUTE_DIM) != 0; - - if ((foreColor & 0xff000000) != 0xff000000) { - // Let bold have bright colors if applicable (one of the first 8): - if (bold && foreColor >= 0 && foreColor < 8) foreColor += 8; - foreColor = palette[foreColor]; - } - - if ((backColor & 0xff000000) != 0xff000000) { - backColor = palette[backColor]; - } - - // Reverse video here if _one and only one_ of the reverse flags are set: - final boolean reverseVideoHere = reverseVideo ^ (effect & (TextStyle.CHARACTER_ATTRIBUTE_INVERSE)) != 0; - if (reverseVideoHere) { - int tmp = foreColor; - foreColor = backColor; - backColor = tmp; - } - - float left = startColumn * mFontWidth; - float right = left + runWidthColumns * mFontWidth; - - mes = mes / mFontWidth; - boolean savedMatrix = false; - if (Math.abs(mes - runWidthColumns) > 0.01) { - canvas.save(); - canvas.scale(runWidthColumns / mes, 1.f); - left *= mes / runWidthColumns; - right *= mes / runWidthColumns; - savedMatrix = true; - } - - if (backColor != palette[TextStyle.COLOR_INDEX_BACKGROUND]) { - // Only draw non-default backgroundColor. - mTextPaint.setColor(backColor); - canvas.drawRect(left, y - mFontLineSpacingAndAscent + mFontAscent, right, y, mTextPaint); - } - - if (cursor != 0) { - mTextPaint.setColor(cursor); - float cursorHeight = mFontLineSpacingAndAscent - mFontAscent; - if (cursorStyle == TerminalEmulator.CURSOR_STYLE_UNDERLINE) cursorHeight /= 4.; - else if (cursorStyle == TerminalEmulator.CURSOR_STYLE_BAR) right -= ((right - left) * 3) / 4.; - canvas.drawRect(left, y - cursorHeight, right, y, mTextPaint); - savedLastDrawnLineX = left; - savedLastDrawnLineY = y; - } - - if ((effect & TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE) == 0) { - if (dim) { - int red = (0xFF & (foreColor >> 16)); - int green = (0xFF & (foreColor >> 8)); - int blue = (0xFF & foreColor); - // Dim color handling used by libvte which in turn took it from xterm - // (https://bug735245.bugzilla-attachments.gnome.org/attachment.cgi?id=284267): - red = red * 2 / 3; - green = green * 2 / 3; - blue = blue * 2 / 3; - foreColor = 0xFF000000 + (red << 16) + (green << 8) + blue; - } - - mTextPaint.setFakeBoldText(bold); - mTextPaint.setUnderlineText(underline); - mTextPaint.setTextSkewX(italic ? -0.35f : 0.f); - mTextPaint.setStrikeThruText(strikeThrough); - mTextPaint.setColor(foreColor); - - // The text alignment is the default Paint.Align.LEFT. - canvas.drawText(text, startCharIndex, runWidthChars, left, y - mFontLineSpacingAndAscent, mTextPaint); - } - - if (savedMatrix) canvas.restore(); + if ((backColor & 0xff000000) != 0xff000000) { + backColor = palette[backColor]; } - float getCursorX() { - return savedLastDrawnLineX; + // Reverse video here if _one and only one_ of the reverse flags are set: + final boolean reverseVideoHere = reverseVideo ^ (effect & (TextStyle.CHARACTER_ATTRIBUTE_INVERSE)) != 0; + if (reverseVideoHere) { + int tmp = foreColor; + foreColor = backColor; + backColor = tmp; } - float getCursorY() { - return savedLastDrawnLineY; + float left = startColumn * mFontWidth; + float right = left + runWidthColumns * mFontWidth; + + mes = mes / mFontWidth; + boolean savedMatrix = false; + if (Math.abs(mes - runWidthColumns) > 0.01) { + canvas.save(); + canvas.scale(runWidthColumns / mes, 1.f); + left *= mes / runWidthColumns; + right *= mes / runWidthColumns; + savedMatrix = true; } + + if (backColor != palette[TextStyle.COLOR_INDEX_BACKGROUND]) { + // Only draw non-default backgroundColor. + mTextPaint.setColor(backColor); + canvas.drawRect(left, y - mFontLineSpacingAndAscent + mFontAscent, right, y, mTextPaint); + } + + if (cursor != 0) { + mTextPaint.setColor(cursor); + float cursorHeight = mFontLineSpacingAndAscent - mFontAscent; + if (cursorStyle == TerminalEmulator.CURSOR_STYLE_UNDERLINE) cursorHeight /= 4.; + else if (cursorStyle == TerminalEmulator.CURSOR_STYLE_BAR) right -= ((right - left) * 3) / 4.; + canvas.drawRect(left, y - cursorHeight, right, y, mTextPaint); + savedLastDrawnLineX = left; + savedLastDrawnLineY = y; + } + + if ((effect & TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE) == 0) { + if (dim) { + int red = (0xFF & (foreColor >> 16)); + int green = (0xFF & (foreColor >> 8)); + int blue = (0xFF & foreColor); + // Dim color handling used by libvte which in turn took it from xterm + // (https://bug735245.bugzilla-attachments.gnome.org/attachment.cgi?id=284267): + red = red * 2 / 3; + green = green * 2 / 3; + blue = blue * 2 / 3; + foreColor = 0xFF000000 + (red << 16) + (green << 8) + blue; + } + + mTextPaint.setFakeBoldText(bold); + mTextPaint.setUnderlineText(underline); + mTextPaint.setTextSkewX(italic ? -0.35f : 0.f); + mTextPaint.setStrikeThruText(strikeThrough); + mTextPaint.setColor(foreColor); + + // The text alignment is the default Paint.Align.LEFT. + canvas.drawText(text, startCharIndex, runWidthChars, left, y - mFontLineSpacingAndAscent, mTextPaint); + } + + if (savedMatrix) canvas.restore(); + } + + float getCursorX() { + return savedLastDrawnLineX; + } + + float getCursorY() { + return savedLastDrawnLineY; + } } diff --git a/app/src/main/java/io/neoterm/frontend/terminal/TerminalView.java b/app/src/main/java/io/neoterm/frontend/terminal/TerminalView.java index fb41bdd..9127825 100755 --- a/app/src/main/java/io/neoterm/frontend/terminal/TerminalView.java +++ b/app/src/main/java/io/neoterm/frontend/terminal/TerminalView.java @@ -15,27 +15,14 @@ import android.text.InputType; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; -import android.view.ActionMode; -import android.view.HapticFeedbackConstants; -import android.view.InputDevice; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; +import android.view.*; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.widget.Scroller; - import io.neoterm.R; -import io.neoterm.backend.EmulatorDebug; -import io.neoterm.backend.KeyHandler; -import io.neoterm.backend.TerminalBuffer; -import io.neoterm.backend.TerminalEmulator; -import io.neoterm.backend.TerminalSession; +import io.neoterm.backend.*; import io.neoterm.frontend.completion.listener.OnAutoCompleteListener; /** @@ -43,1081 +30,1081 @@ import io.neoterm.frontend.completion.listener.OnAutoCompleteListener; */ public final class TerminalView extends View { - /** - * Log view key and IME events. - */ - private static final boolean LOG_KEY_EVENTS = false; + /** + * Log view key and IME events. + */ + private static final boolean LOG_KEY_EVENTS = false; - /** - * The currently displayed terminal session, whose emulator is {@link #mEmulator}. - */ - TerminalSession mTermSession; + /** + * The currently displayed terminal session, whose emulator is {@link #mEmulator}. + */ + TerminalSession mTermSession; - /** - * Our terminal emulator whose session is {@link #mTermSession}. - */ - TerminalEmulator mEmulator; + /** + * Our terminal emulator whose session is {@link #mTermSession}. + */ + TerminalEmulator mEmulator; - TerminalRenderer mRenderer; + TerminalRenderer mRenderer; - TerminalViewClient mClient; + TerminalViewClient mClient; - /** - * The top row of text to display. Ranges from -activeTranscriptRows to 0. - */ - int mTopRow; + /** + * The top row of text to display. Ranges from -activeTranscriptRows to 0. + */ + int mTopRow; - boolean mIsSelectingText = false, mIsDraggingLeftSelection, mInitialTextSelection; - int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1; - float mSelectionDownX, mSelectionDownY; - private ActionMode mActionMode; - private BitmapDrawable mLeftSelectionHandle, mRightSelectionHandle; + boolean mIsSelectingText = false, mIsDraggingLeftSelection, mInitialTextSelection; + int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1; + float mSelectionDownX, mSelectionDownY; + private ActionMode mActionMode; + private BitmapDrawable mLeftSelectionHandle, mRightSelectionHandle; - float mScaleFactor = 1.f; - /* final */ GestureAndScaleRecognizer mGestureRecognizer; + float mScaleFactor = 1.f; + /* final */ GestureAndScaleRecognizer mGestureRecognizer; - /** - * Keep track of where mouse touch event started which we report as mouse scroll. - */ - private int mMouseScrollStartX = -1, mMouseScrollStartY = -1; - /** - * Keep track of the time when a touch event leading to sending mouse scroll events started. - */ - private long mMouseStartDownTime = -1; + /** + * Keep track of where mouse touch event started which we report as mouse scroll. + */ + private int mMouseScrollStartX = -1, mMouseScrollStartY = -1; + /** + * Keep track of the time when a touch event leading to sending mouse scroll events started. + */ + private long mMouseStartDownTime = -1; - /* final */ Scroller mScroller; + /* final */ Scroller mScroller; - /** - * What was left in from scrolling movement. - */ - float mScrollRemainder; + /** + * What was left in from scrolling movement. + */ + float mScrollRemainder; - /** - * If non-zero, this is the last unicode code point received if that was a combining character. - */ - int mCombiningAccent; - int mTextSize; + /** + * If non-zero, this is the last unicode code point received if that was a combining character. + */ + int mCombiningAccent; + int mTextSize; - /** - * If true, IME will be word based instead of char based. - */ - private boolean mEnableWordBasedIme = false; + /** + * If true, IME will be word based instead of char based. + */ + private boolean mEnableWordBasedIme = false; - private boolean mAccessibilityEnabled; + private boolean mAccessibilityEnabled; - public TerminalView(Context context) { - super(context); - commonInit(context); - } + public TerminalView(Context context) { + super(context); + commonInit(context); + } - public TerminalView(Context context, AttributeSet attributeSet) { // NO_UCD (unused code) - super(context, attributeSet); - commonInit(context); - } + public TerminalView(Context context, AttributeSet attributeSet) { // NO_UCD (unused code) + super(context, attributeSet); + commonInit(context); + } - private void commonInit(Context context) { - mGestureRecognizer = new GestureAndScaleRecognizer(context, new GestureAndScaleRecognizer.Listener() { + private void commonInit(Context context) { + mGestureRecognizer = new GestureAndScaleRecognizer(context, new GestureAndScaleRecognizer.Listener() { - private boolean scrolledWithFinger; + private boolean scrolledWithFinger; - // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event - // e.g in vim, we can change window size with fingers moving. - private float doubleTapX, doubleTapY; - private boolean draggedAfterDoubleTap; + // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event + // e.g in vim, we can change window size with fingers moving. + private float doubleTapX, doubleTapY; + private boolean draggedAfterDoubleTap; - @Override - public boolean onUp(MotionEvent e) { - mScrollRemainder = 0.0f; - // 只有在没有选中文字的时候可以发送鼠标事件: !isSelectingText - if (mEmulator != null && mEmulator.isMouseTrackingActive() && !mIsSelectingText && !scrolledWithFinger) { - // Quick event processing when mouse tracking is active - do not wait for check of double tapping - // for zooming. - sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, true); - sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, false); - return true; - } - scrolledWithFinger = false; - return false; - } - - @Override - public boolean onSingleTapUp(MotionEvent e) { - if (mEmulator == null) return true; - if (mIsSelectingText) { - toggleSelectingText(null); - return true; - } - requestFocus(); - if (!mEmulator.isMouseTrackingActive()) { - if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) { - mClient.onSingleTapUp(e); - return true; - } - } - return false; - } - - @Override - public boolean onScroll(MotionEvent e, float distanceX, float distanceY) { - // 如果在选择文字时,不允许滑动屏幕,因为文字选择器需要滑动 - if (mEmulator == null || mIsSelectingText) return true; - - if (mEmulator.isMouseTrackingActive() && e.isFromSource(InputDevice.SOURCE_MOUSE)) { - // 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, - // since we cannot just start sending these events without a starting press event, - // which we do not do for touch input, only mouse in onTouchEvent(). - sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON_MOVED, true); - } else { - scrolledWithFinger = true; - distanceY += mScrollRemainder; - int deltaRows = (int) (distanceY / mRenderer.mFontLineSpacing); - - // 记住当前滑动到的位置 - mScrollRemainder = distanceY - deltaRows * mRenderer.mFontLineSpacing; - doScroll(e, deltaRows); - } - return true; - } - - @Override - public boolean onScale(float focusX, float focusY, float scale) { - if (mEmulator == null || mIsSelectingText) return true; - mScaleFactor *= scale; - // 这里一般是改变文字大小 - mScaleFactor = mClient.onScale(mScaleFactor); - return true; - } - - @Override - public boolean onFling(final MotionEvent e2, float velocityX, float velocityY) { - // 选择文字时,文字选择器会用到触摸操作,这里不管 - if (mEmulator == null || mIsSelectingText) return true; - - // Do not start scrolling until last fling has been taken care of: - if (!mScroller.isFinished()) return true; - - final boolean mouseTrackingAtStartOfFling = mEmulator.isMouseTrackingActive(); - float SCALE = 0.25f; - if (mouseTrackingAtStartOfFling) { - mScroller.fling(0, 0, 0, -(int) (velocityY * SCALE), 0, 0, -mEmulator.mRows / 2, mEmulator.mRows / 2); - } else { - mScroller.fling(0, mTopRow, 0, -(int) (velocityY * SCALE), 0, 0, -mEmulator.getScreen().getActiveTranscriptRows(), 0); - } - - post(new Runnable() { - private int mLastY = 0; - - @Override - public void run() { - if (mouseTrackingAtStartOfFling != mEmulator.isMouseTrackingActive()) { - mScroller.abortAnimation(); - return; - } - if (mScroller.isFinished()) return; - boolean more = mScroller.computeScrollOffset(); - int newY = mScroller.getCurrY(); - int diff = mouseTrackingAtStartOfFling ? (newY - mLastY) : (newY - mTopRow); - doScroll(e2, diff); - mLastY = newY; - if (more) post(this); - } - }); - - return true; - } - - @Override - public boolean onDown(float x, float y) { - return false; - } - - @Override - public boolean onDoubleTap(MotionEvent e) { - // Old behavior: Do not treat is as a single confirmed tap - it may be followed by zoom. - - // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event - // e.g in vim, we can change window size with fingers moving. - // Now double tap and drag has been treated as a MOUSE_LEFT_BUTTON_MOVED event. - return true; - } - - // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event - // e.g in vim, we can change window size with fingers moving. - @Override - public boolean onDoubleTapEvent(MotionEvent e) { - if (mEmulator.isMouseTrackingActive() && !e.isFromSource(InputDevice.SOURCE_MOUSE)) { - switch (e.getAction()) { - case MotionEvent.ACTION_DOWN: - doubleTapX = e.getX(); - doubleTapY = e.getY(); - draggedAfterDoubleTap = false; - sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, true); - break; - case MotionEvent.ACTION_UP: - if (!draggedAfterDoubleTap) { - sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, false); - sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, true); - } - sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, false); - break; - case MotionEvent.ACTION_MOVE: - if (Math.abs(e.getX() - doubleTapX) >= mRenderer.mFontWidth - || Math.abs(e.getY() - doubleTapY) >= mRenderer.mFontLineSpacing) { - doubleTapX = e.getX(); - doubleTapY = e.getY(); - draggedAfterDoubleTap = true; - sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON_MOVED, true); - } - break; - } - } - return true; - } - - @Override - public void onLongPress(MotionEvent e) { - if (mGestureRecognizer.isInProgress()) return; - if (mClient.onLongPress(e)) return; - if (!mIsSelectingText) { - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - toggleSelectingText(e); - } - } - }); - mScroller = new Scroller(context); - AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); - mAccessibilityEnabled = am != null && am.isEnabled(); - } - - /** - * @param client Listener for all kinds of key events, both hardware and IME (which makes it different from that - * available with {@link View#setOnKeyListener(OnKeyListener)}. - */ - public void setTerminalViewClient(TerminalViewClient client) { - this.mClient = client; - } - - @Override - public void setOnKeyListener(OnKeyListener l) { - if (l instanceof TerminalViewClient) { - setTerminalViewClient(((TerminalViewClient) l)); + @Override + public boolean onUp(MotionEvent e) { + mScrollRemainder = 0.0f; + // 只有在没有选中文字的时候可以发送鼠标事件: !isSelectingText + if (mEmulator != null && mEmulator.isMouseTrackingActive() && !mIsSelectingText && !scrolledWithFinger) { + // Quick event processing when mouse tracking is active - do not wait for check of double tapping + // for zooming. + sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, true); + sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, false); + return true; } - } + scrolledWithFinger = false; + return false; + } - /** - * Attach a {@link TerminalSession} to this view. - * - * @param session The {@link TerminalSession} this view will be displaying. - */ - public boolean attachSession(TerminalSession session) { - if (session == mTermSession) return false; - mTopRow = 0; - - mTermSession = session; - mEmulator = null; - mCombiningAccent = 0; - - updateSize(); - - // Wait with enabling the scrollbar until we have a terminal to get scroll position from. - setVerticalScrollBarEnabled(true); - - return true; - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - // Using InputType.NULL is the most correct input type and avoids issues with other hacks. - // - // Previous keyboard issues: - // https://github.com/termux/termux-packages/issues/25 - // https://github.com/termux/termux-app/issues/87. - // https://github.com/termux/termux-app/issues/126. - // https://github.com/termux/termux-app/issues/137 (japanese chars and TYPE_NULL). - if (mEnableWordBasedIme) { - // Workaround for Google Pinying cannot input Chinese - outAttrs.inputType = InputType.TYPE_CLASS_TEXT; - } else { - outAttrs.inputType = InputType.TYPE_NULL; + @Override + public boolean onSingleTapUp(MotionEvent e) { + if (mEmulator == null) return true; + if (mIsSelectingText) { + toggleSelectingText(null); + return true; } - - // Note that IME_ACTION_NONE cannot be used as that makes it impossible to input newlines using the on-screen - // keyboard on Android TV (see https://github.com/termux/termux-app/issues/221). - outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; - - return new BaseInputConnection(this, true) { - @Override - public boolean finishComposingText() { - if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: finishComposingText()"); - super.finishComposingText(); - - sendTextToTerminal(getEditable()); - getEditable().clear(); - return true; - } - - @Override - public boolean commitText(CharSequence text, int newCursorPosition) { - if (LOG_KEY_EVENTS) { - Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")"); - } - super.commitText(text, newCursorPosition); - - if (mEmulator == null) return true; - - Editable content = getEditable(); - sendTextToTerminal(content); - if (onAutoCompleteListener != null) { - onAutoCompleteListener.onCompletionRequired(content.toString()); - } - content.clear(); - return true; - } - - @Override - public boolean deleteSurroundingText(int leftLength, int rightLength) { - if (LOG_KEY_EVENTS) { - Log.i(EmulatorDebug.LOG_TAG, "IME: deleteSurroundingText(" + leftLength + ", " + rightLength + ")"); - } - // The stock Samsung keyboard with 'Auto check spelling' enabled sends leftLength > 1. - KeyEvent deleteKey = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL); - for (int i = 0; i < leftLength; i++) sendKeyEvent(deleteKey); - return super.deleteSurroundingText(leftLength, rightLength); - } - - void sendTextToTerminal(CharSequence text) { - final int textLengthInChars = text.length(); - for (int i = 0; i < textLengthInChars; i++) { - char firstChar = text.charAt(i); - int codePoint; - if (Character.isHighSurrogate(firstChar)) { - if (++i < textLengthInChars) { - codePoint = Character.toCodePoint(firstChar, text.charAt(i)); - } else { - // At end of string, with no low surrogate following the high: - codePoint = TerminalEmulator.UNICODE_REPLACEMENT_CHAR; - } - } else { - codePoint = firstChar; - } - - boolean ctrlHeld = false; - if (codePoint <= 31 && codePoint != 27) { - if (codePoint == '\n') { - // The AOSP keyboard and descendants seems to send \n as text when the enter key is pressed, - // instead of a key event like most other keyboard apps. A terminal expects \r for the enter - // key (although when icrnl is enabled this doesn't make a difference - run 'stty -icrnl' to - // check the behaviour). - codePoint = '\r'; - } - - // E.g. penti keyboard for ctrl input. - ctrlHeld = true; - switch (codePoint) { - case 31: - codePoint = '_'; - break; - case 30: - codePoint = '^'; - break; - case 29: - codePoint = ']'; - break; - case 28: - codePoint = '\\'; - break; - default: - codePoint += 96; - break; - } - } - - inputCodePoint(codePoint, ctrlHeld, false); - } - } - - }; - } - - @Override - protected int computeVerticalScrollRange() { - return mEmulator == null ? 1 : mEmulator.getScreen().getActiveRows(); - } - - @Override - protected int computeVerticalScrollExtent() { - return mEmulator == null ? 1 : mEmulator.mRows; - } - - @Override - protected int computeVerticalScrollOffset() { - return mEmulator == null ? 1 : mEmulator.getScreen().getActiveRows() + mTopRow - mEmulator.mRows; - } - - public void onScreenUpdated() { - if (mEmulator == null) return; - boolean skipScrolling = false; - boolean isScreenHeld = false; - - // currentScroll 记录了当前滚动到的位置 - // expectedScroll 记录了假设一直跟随输出滚动在最底部时的滚动位置 - // 如果二者不一样,即 mTop != 0,则说明用户在脚本输出的时候滚动了屏幕 - // 很有可能时用户需要观察上面脚本的输出结果 - // 那么这个时候我们就不跟随输出滚动屏幕 - // int currentScroll = computeVerticalScrollOffset(); - // int expectedScroll = mEmulator.getScreen().getActiveRows() - mEmulator.mRows; - - if (mTopRow != 0) { - isScreenHeld = true; - } - - if (mIsSelectingText || isScreenHeld) { - // Do not scroll when selecting text. - int rowsInHistory = mEmulator.getScreen().getActiveTranscriptRows(); - int rowShift = mEmulator.getScrollCounter(); - if (-mTopRow + rowShift > rowsInHistory) { - // .. unless we're hitting the end of history transcript, in which - // case we abort text selection and scroll to end. - - // 只当是因为选择文字而停止滚动时才取消选择文字 - if (mIsSelectingText) { - toggleSelectingText(null); - } - } else { - skipScrolling = true; - mTopRow -= rowShift; - mSelY1 -= rowShift; - mSelY2 -= rowShift; - } - - // 不滚动屏幕,但要让滚动条显示来告诉用户脚本在输出 - if (isScreenHeld) { - awakenScrollBars(); - } - } - - if (!skipScrolling && mTopRow != 0) { - // 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; - } - - mEmulator.clearScrollCounter(); - invalidate(); - - // Basic accessibility service - String contentText = mEmulator.getScreen() - .getSelectedText(0, mTopRow, mEmulator.mColumns, mTopRow + mEmulator.mRows); - if (mAccessibilityEnabled) { - setContentDescription(contentText); - } - } - - public int getTextSize() { - return mTextSize; - } - - /** - * Sets the text size, which in turn sets the number of rows and columns. - * - * @param textSize the new font size, in density-independent pixels. - */ - public void setTextSize(int textSize) { - this.mTextSize = textSize; - mRenderer = new TerminalRenderer(textSize, mRenderer == null ? Typeface.MONOSPACE : mRenderer.mTypeface); - updateSize(); - } - - public void setTypeface(Typeface newTypeface) { - mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface); - updateSize(); - invalidate(); - } - - @Override - public boolean onCheckIsTextEditor() { - return true; - } - - @Override - public boolean isOpaque() { - return true; - } - - /** - * Send a single mouse event code to the terminal. - */ - void sendMouseEventCode(MotionEvent e, int button, boolean pressed) { - int x = (int) (e.getX() / mRenderer.mFontWidth) + 1; - int y = (int) ((e.getY() - mRenderer.mFontLineSpacingAndAscent) / mRenderer.mFontLineSpacing) + 1; - if (pressed && (button == TerminalEmulator.MOUSE_WHEELDOWN_BUTTON || button == TerminalEmulator.MOUSE_WHEELUP_BUTTON)) { - if (mMouseStartDownTime == e.getDownTime()) { - x = mMouseScrollStartX; - y = mMouseScrollStartY; - } else { - mMouseStartDownTime = e.getDownTime(); - mMouseScrollStartX = x; - mMouseScrollStartY = y; - } - } - mEmulator.sendMouseEvent(button, x, y, pressed); - } - - /** - * Perform a scroll, either from dragging the screen or by scrolling a mouse wheel. - */ - void doScroll(MotionEvent event, int rowsDown) { - boolean up = rowsDown < 0; - int amount = Math.abs(rowsDown); - for (int i = 0; i < amount; i++) { - if (mEmulator.isMouseTrackingActive()) { - sendMouseEventCode(event, up ? TerminalEmulator.MOUSE_WHEELUP_BUTTON : TerminalEmulator.MOUSE_WHEELDOWN_BUTTON, true); - } else if (mEmulator.isAlternateBufferActive()) { - // Send up and down key events for scrolling, which is what some terminals do to make scroll work in - // e.g. less, which shifts to the alt screen without mouse handling. - handleKeyCode(up ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN, 0); - } else { - mTopRow = Math.min(0, Math.max(-(mEmulator.getScreen().getActiveTranscriptRows()), mTopRow + (up ? -1 : 1))); - if (!awakenScrollBars()) invalidate(); - } - } - } - - /** - * Overriding {@link View#onGenericMotionEvent(MotionEvent)}. - */ - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - if (mEmulator != null && event.isFromSource(InputDevice.SOURCE_MOUSE) && event.getAction() == MotionEvent.ACTION_SCROLL) { - // Handle mouse wheel scrolling. - boolean up = event.getAxisValue(MotionEvent.AXIS_VSCROLL) > 0.0f; - doScroll(event, up ? -3 : 3); + requestFocus(); + if (!mEmulator.isMouseTrackingActive()) { + if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) { + mClient.onSingleTapUp(e); return true; + } } return false; - } + } - @SuppressLint("ClickableViewAccessibility") - @Override - @TargetApi(23) - public boolean onTouchEvent(MotionEvent ev) { - if (mEmulator == null) return true; - final int action = ev.getAction(); + @Override + public boolean onScroll(MotionEvent e, float distanceX, float distanceY) { + // 如果在选择文字时,不允许滑动屏幕,因为文字选择器需要滑动 + if (mEmulator == null || mIsSelectingText) return true; - if (mIsSelectingText) { - int cy = (int) (ev.getY() / mRenderer.mFontLineSpacing) + mTopRow; - int cx = (int) (ev.getX() / mRenderer.mFontWidth); + if (mEmulator.isMouseTrackingActive() && e.isFromSource(InputDevice.SOURCE_MOUSE)) { + // 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, + // since we cannot just start sending these events without a starting press event, + // which we do not do for touch input, only mouse in onTouchEvent(). + sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON_MOVED, true); + } else { + scrolledWithFinger = true; + distanceY += mScrollRemainder; + int deltaRows = (int) (distanceY / mRenderer.mFontLineSpacing); - switch (action) { - case MotionEvent.ACTION_UP: - mInitialTextSelection = false; - break; - case MotionEvent.ACTION_DOWN: - int distanceFromSel1 = Math.abs(cx - mSelX1) + Math.abs(cy - mSelY1); - int distanceFromSel2 = Math.abs(cx - mSelX2) + Math.abs(cy - mSelY2); - mIsDraggingLeftSelection = distanceFromSel1 <= distanceFromSel2; - mSelectionDownX = ev.getX(); - mSelectionDownY = ev.getY(); - break; - case MotionEvent.ACTION_MOVE: - if (mInitialTextSelection) break; - float deltaX = ev.getX() - mSelectionDownX; - float deltaY = ev.getY() - mSelectionDownY; - int deltaCols = (int) Math.ceil(deltaX / mRenderer.mFontWidth); - int deltaRows = (int) Math.ceil(deltaY / mRenderer.mFontLineSpacing); - mSelectionDownX += deltaCols * mRenderer.mFontWidth; - mSelectionDownY += deltaRows * mRenderer.mFontLineSpacing; - if (mIsDraggingLeftSelection) { - mSelX1 += deltaCols; - mSelY1 += deltaRows; - } else { - mSelX2 += deltaCols; - mSelY2 += deltaRows; - } - - mSelX1 = Math.min(mEmulator.mColumns, Math.max(0, mSelX1)); - mSelX2 = Math.min(mEmulator.mColumns, Math.max(0, mSelX2)); - - if (mSelY1 == mSelY2 && mSelX1 > mSelX2 || mSelY1 > mSelY2) { - // Switch handles. - mIsDraggingLeftSelection = !mIsDraggingLeftSelection; - int tmpX1 = mSelX1, tmpY1 = mSelY1; - mSelX1 = mSelX2; - mSelY1 = mSelY2; - mSelX2 = tmpX1; - mSelY2 = tmpY1; - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - mActionMode.invalidateContentRect(); - invalidate(); - break; - default: - break; - } - mGestureRecognizer.onTouchEvent(ev); - return true; - } else if (ev.isFromSource(InputDevice.SOURCE_MOUSE)) { - if (ev.isButtonPressed(MotionEvent.BUTTON_SECONDARY)) { - if (action == MotionEvent.ACTION_DOWN) showContextMenu(); - return true; - } else if (ev.isButtonPressed(MotionEvent.BUTTON_TERTIARY)) { - pasteFromClipboard(); - } 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; - } + // 记住当前滑动到的位置 + mScrollRemainder = distanceY - deltaRows * mRenderer.mFontLineSpacing; + doScroll(e, deltaRows); } - - mGestureRecognizer.onTouchEvent(ev); return true; - } + } - public void pasteFromClipboard() { - ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); - if (clipboard == null) { - return; - } - ClipData clipData = clipboard.getPrimaryClip(); - if (clipData != null) { - CharSequence paste = clipData.getItemAt(0).coerceToText(getContext()); - if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString()); - } - } + @Override + public boolean onScale(float focusX, float focusY, float scale) { + if (mEmulator == null || mIsSelectingText) return true; + mScaleFactor *= scale; + // 这里一般是改变文字大小 + mScaleFactor = mClient.onScale(mScaleFactor); + return true; + } - @Override - public boolean onKeyPreIme(int keyCode, KeyEvent event) { - if (LOG_KEY_EVENTS) - Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")"); - if (keyCode == KeyEvent.KEYCODE_BACK) { - if (mIsSelectingText) { - toggleSelectingText(null); - return true; - } else if (mClient.shouldBackButtonBeMappedToEscape()) { - // Intercept back button to treat it as escape: - switch (event.getAction()) { - case KeyEvent.ACTION_DOWN: - return onKeyDown(keyCode, event); - case KeyEvent.ACTION_UP: - return onKeyUp(keyCode, event); - } - } - } - return super.onKeyPreIme(keyCode, event); - } + @Override + public boolean onFling(final MotionEvent e2, float velocityX, float velocityY) { + // 选择文字时,文字选择器会用到触摸操作,这里不管 + if (mEmulator == null || mIsSelectingText) return true; - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (LOG_KEY_EVENTS) - Log.i(EmulatorDebug.LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")"); - if (mEmulator == null) return true; + // Do not start scrolling until last fling has been taken care of: + if (!mScroller.isFinished()) return true; - if (mClient.onKeyDown(keyCode, event, mTermSession)) { - invalidate(); - return true; - } else if (event.isSystem() && (!mClient.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) { - return super.onKeyDown(keyCode, event); - } else if (event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) { - mTermSession.write(event.getCharacters()); - return true; - } - - final int metaState = event.getMetaState(); - final boolean controlDownFromEvent = event.isCtrlPressed(); - final boolean leftAltDownFromEvent = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0; - final boolean rightAltDownFromEvent = (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0; - - int keyMod = 0; - if (controlDownFromEvent) keyMod |= KeyHandler.KEYMOD_CTRL; - if (event.isAltPressed()) keyMod |= KeyHandler.KEYMOD_ALT; - if (event.isShiftPressed()) keyMod |= KeyHandler.KEYMOD_SHIFT; - if (!event.isFunctionPressed() && handleKeyCode(keyCode, keyMod)) { - if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "handleKeyCode() took key event"); - return true; - } - - // Clear Ctrl since we handle that ourselves: - int bitsToClear = KeyEvent.META_CTRL_MASK; - if (rightAltDownFromEvent) { - // Let right Alt/Alt Gr be used to compose characters. + final boolean mouseTrackingAtStartOfFling = mEmulator.isMouseTrackingActive(); + float SCALE = 0.25f; + if (mouseTrackingAtStartOfFling) { + mScroller.fling(0, 0, 0, -(int) (velocityY * SCALE), 0, 0, -mEmulator.mRows / 2, mEmulator.mRows / 2); } else { - // Use left alt to send to terminal (e.g. Left Alt+B to jump back a word), so remove: - bitsToClear |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON; - } - int effectiveMetaState = event.getMetaState() & ~bitsToClear; - - int result = event.getUnicodeChar(effectiveMetaState); - if (LOG_KEY_EVENTS) - Log.i(EmulatorDebug.LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result); - if (result == 0) { - return false; + mScroller.fling(0, mTopRow, 0, -(int) (velocityY * SCALE), 0, 0, -mEmulator.getScreen().getActiveTranscriptRows(), 0); } - int oldCombiningAccent = mCombiningAccent; - if ((result & KeyCharacterMap.COMBINING_ACCENT) != 0) { - // If entered combining accent previously, write it out: - if (mCombiningAccent != 0) - inputCodePoint(mCombiningAccent, controlDownFromEvent, leftAltDownFromEvent); - mCombiningAccent = result & KeyCharacterMap.COMBINING_ACCENT_MASK; - } else { - if (mCombiningAccent != 0) { - int combinedChar = KeyCharacterMap.getDeadChar(mCombiningAccent, result); - if (combinedChar > 0) result = combinedChar; - mCombiningAccent = 0; + post(new Runnable() { + private int mLastY = 0; + + @Override + public void run() { + if (mouseTrackingAtStartOfFling != mEmulator.isMouseTrackingActive()) { + mScroller.abortAnimation(); + return; } - inputCodePoint(result, controlDownFromEvent, leftAltDownFromEvent); - } - - if (mCombiningAccent != oldCombiningAccent) invalidate(); - - if (onAutoCompleteListener != null) { - if (event.isPrintingKey()) { - char printingChar = (char) event.getUnicodeChar(metaState); - if (printingChar != '\b') { - // ASCII chars - onAutoCompleteListener.onCompletionRequired(new String(new char[]{printingChar})); - } - } - } + if (mScroller.isFinished()) return; + boolean more = mScroller.computeScrollOffset(); + int newY = mScroller.getCurrY(); + int diff = mouseTrackingAtStartOfFling ? (newY - mLastY) : (newY - mTopRow); + doScroll(e2, diff); + mLastY = newY; + if (more) post(this); + } + }); return true; + } + + @Override + public boolean onDown(float x, float y) { + return false; + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + // Old behavior: Do not treat is as a single confirmed tap - it may be followed by zoom. + + // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event + // e.g in vim, we can change window size with fingers moving. + // Now double tap and drag has been treated as a MOUSE_LEFT_BUTTON_MOVED event. + return true; + } + + // For treating double tap as MOUSE_LEFT_BUTTON_MOVED event + // e.g in vim, we can change window size with fingers moving. + @Override + public boolean onDoubleTapEvent(MotionEvent e) { + if (mEmulator.isMouseTrackingActive() && !e.isFromSource(InputDevice.SOURCE_MOUSE)) { + switch (e.getAction()) { + case MotionEvent.ACTION_DOWN: + doubleTapX = e.getX(); + doubleTapY = e.getY(); + draggedAfterDoubleTap = false; + sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, true); + break; + case MotionEvent.ACTION_UP: + if (!draggedAfterDoubleTap) { + sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, false); + sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, true); + } + sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, false); + break; + case MotionEvent.ACTION_MOVE: + if (Math.abs(e.getX() - doubleTapX) >= mRenderer.mFontWidth + || Math.abs(e.getY() - doubleTapY) >= mRenderer.mFontLineSpacing) { + doubleTapX = e.getX(); + doubleTapY = e.getY(); + draggedAfterDoubleTap = true; + sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON_MOVED, true); + } + break; + } + } + return true; + } + + @Override + public void onLongPress(MotionEvent e) { + if (mGestureRecognizer.isInProgress()) return; + if (mClient.onLongPress(e)) return; + if (!mIsSelectingText) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + toggleSelectingText(e); + } + } + }); + mScroller = new Scroller(context); + AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); + mAccessibilityEnabled = am != null && am.isEnabled(); + } + + /** + * @param client Listener for all kinds of key events, both hardware and IME (which makes it different from that + * available with {@link View#setOnKeyListener(OnKeyListener)}. + */ + public void setTerminalViewClient(TerminalViewClient client) { + this.mClient = client; + } + + @Override + public void setOnKeyListener(OnKeyListener l) { + if (l instanceof TerminalViewClient) { + setTerminalViewClient(((TerminalViewClient) l)); + } + } + + /** + * Attach a {@link TerminalSession} to this view. + * + * @param session The {@link TerminalSession} this view will be displaying. + */ + public boolean attachSession(TerminalSession session) { + if (session == mTermSession) return false; + mTopRow = 0; + + mTermSession = session; + mEmulator = null; + mCombiningAccent = 0; + + updateSize(); + + // Wait with enabling the scrollbar until we have a terminal to get scroll position from. + setVerticalScrollBarEnabled(true); + + return true; + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + // Using InputType.NULL is the most correct input type and avoids issues with other hacks. + // + // Previous keyboard issues: + // https://github.com/termux/termux-packages/issues/25 + // https://github.com/termux/termux-app/issues/87. + // https://github.com/termux/termux-app/issues/126. + // https://github.com/termux/termux-app/issues/137 (japanese chars and TYPE_NULL). + if (mEnableWordBasedIme) { + // Workaround for Google Pinying cannot input Chinese + outAttrs.inputType = InputType.TYPE_CLASS_TEXT; + } else { + outAttrs.inputType = InputType.TYPE_NULL; } - void inputCodePoint(int codePoint, boolean controlDownFromEvent, boolean leftAltDownFromEvent) { + // Note that IME_ACTION_NONE cannot be used as that makes it impossible to input newlines using the on-screen + // keyboard on Android TV (see https://github.com/termux/termux-app/issues/221). + outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; + + return new BaseInputConnection(this, true) { + @Override + public boolean finishComposingText() { + if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: finishComposingText()"); + super.finishComposingText(); + + sendTextToTerminal(getEditable()); + getEditable().clear(); + return true; + } + + @Override + public boolean commitText(CharSequence text, int newCursorPosition) { if (LOG_KEY_EVENTS) { - Log.i(EmulatorDebug.LOG_TAG, "inputCodePoint(codePoint=" + codePoint + ", controlDownFromEvent=" + controlDownFromEvent + ", leftAltDownFromEvent=" - + leftAltDownFromEvent + ")"); + Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")"); } + super.commitText(text, newCursorPosition); - if (mTermSession == null) return; - - final boolean controlDown = controlDownFromEvent || mClient.readControlKey(); - final boolean altDown = leftAltDownFromEvent || mClient.readAltKey(); - - if (mClient.onCodePoint(codePoint, controlDown, mTermSession)) return; - - if (controlDown) { - if (codePoint >= 'a' && codePoint <= 'z') { - codePoint = codePoint - 'a' + 1; - } else if (codePoint >= 'A' && codePoint <= 'Z') { - codePoint = codePoint - 'A' + 1; - } else if (codePoint == ' ' || codePoint == '2') { - codePoint = 0; - } else if (codePoint == '[' || codePoint == '3') { - codePoint = 27; // ^[ (Esc) - } else if (codePoint == '\\' || codePoint == '4') { - codePoint = 28; - } else if (codePoint == ']' || codePoint == '5') { - codePoint = 29; - } else if (codePoint == '^' || codePoint == '6') { - codePoint = 30; // control-^ - } else if (codePoint == '_' || codePoint == '7' || codePoint == '/') { - // "Ctrl-/ sends 0x1f which is equivalent of Ctrl-_ since the days of VT102" - // - http://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal - codePoint = 31; - } else if (codePoint == '8') { - codePoint = 127; // DEL - } - } - - if (codePoint > -1) { - // Work around bluetooth keyboards sending funny unicode characters instead - // of the more normal ones from ASCII that terminal programs expect - the - // desire to input the original characters should be low. - switch (codePoint) { - case 0x02DC: // SMALL TILDE. - codePoint = 0x007E; // TILDE (~). - 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: - mTermSession.writeCodePoint(altDown, codePoint); - scrollToBottomIfNeeded(); - } - } - - /** - * Input the specified keyCode if applicable and return if the input was consumed. - */ - public boolean handleKeyCode(int keyCode, int keyMod) { - TerminalEmulator term = mTermSession.getEmulator(); - String code = KeyHandler.getCode(keyCode, keyMod, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode()); - if (code == null) return false; - mTermSession.write(code); - scrollToBottomIfNeeded(); - if (onAutoCompleteListener != null) { - onAutoCompleteListener.onKeyCode(keyCode, keyMod); - } - return true; - } - - /** - * Called when a key is released in the view. - * - * @param keyCode The keycode of the key which was released. - * @param event A {@link KeyEvent} describing the event. - * @return Whether the event was handled. - */ - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (LOG_KEY_EVENTS) - Log.i(EmulatorDebug.LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")"); if (mEmulator == null) return true; - if (mClient.onKeyUp(keyCode, event)) { - invalidate(); - return true; - } else if (event.isSystem()) { - // Let system key events through. - return super.onKeyUp(keyCode, event); + Editable content = getEditable(); + sendTextToTerminal(content); + if (onAutoCompleteListener != null) { + onAutoCompleteListener.onCompletionRequired(content.toString()); } - + content.clear(); return true; - } + } - /** - * 每次处理用户按下对终端输出有影响的键时被调用 - * 如果当前屏幕不处于最底部,则自动滚动到最底部 - */ - void scrollToBottomIfNeeded() { - if (mTopRow != 0) { - mTopRow = 0; - mEmulator.clearScrollCounter(); - invalidate(); + @Override + public boolean deleteSurroundingText(int leftLength, int rightLength) { + if (LOG_KEY_EVENTS) { + Log.i(EmulatorDebug.LOG_TAG, "IME: deleteSurroundingText(" + leftLength + ", " + rightLength + ")"); } - } + // The stock Samsung keyboard with 'Auto check spelling' enabled sends leftLength > 1. + KeyEvent deleteKey = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL); + for (int i = 0; i < leftLength; i++) sendKeyEvent(deleteKey); + return super.deleteSurroundingText(leftLength, rightLength); + } - /** - * This is called during layout when the size of this view has changed. If you were just added to the view - * hierarchy, you're called with the old values of 0. - */ - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - updateSize(); - } - - /** - * Check if the terminal size in rows and columns should be updated. - */ - public void updateSize() { - int viewWidth = getWidth(); - int viewHeight = getHeight(); - if (viewWidth == 0 || viewHeight == 0 || mTermSession == null) return; - - // Set to 80 and 24 if you want to enable vttest. - int newColumns = Math.max(4, (int) (viewWidth / mRenderer.mFontWidth)); - int newRows = Math.max(4, (viewHeight - mRenderer.mFontLineSpacingAndAscent) / mRenderer.mFontLineSpacing); - - if (mEmulator == null || (newColumns != mEmulator.mColumns || newRows != mEmulator.mRows)) { - mTermSession.updateSize(newColumns, newRows); - mEmulator = mTermSession.getEmulator(); - - mTopRow = 0; - scrollTo(0, 0); - invalidate(); - } - } - - @Override - protected void onDraw(Canvas canvas) { - if (mEmulator == null) { - canvas.drawColor(0XFF000000); - } else { - 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. - */ - @TargetApi(23) - public void toggleSelectingText(MotionEvent ev) { - mIsSelectingText = !mIsSelectingText; - mClient.copyModeChanged(mIsSelectingText); - - if (mIsSelectingText) { - if (mLeftSelectionHandle == null) { - mLeftSelectionHandle = (BitmapDrawable) getContext().getDrawable(R.drawable.text_select_handle_left_material); - mRightSelectionHandle = (BitmapDrawable) getContext().getDrawable(R.drawable.text_select_handle_right_material); - } - - int cx = (int) (ev.getX() / mRenderer.mFontWidth); - final boolean eventFromMouse = ev.isFromSource(InputDevice.SOURCE_MOUSE); - // Offset for finger: - final int SELECT_TEXT_OFFSET_Y = eventFromMouse ? 0 : -40; - int cy = (int) ((ev.getY() + SELECT_TEXT_OFFSET_Y) / mRenderer.mFontLineSpacing) + mTopRow; - - mSelX1 = mSelX2 = cx; - mSelY1 = mSelY2 = cy; - - TerminalBuffer screen = mEmulator.getScreen(); - if (!" ".equals(screen.getSelectedText(mSelX1, mSelY1, mSelX1, mSelY1))) { - // Selecting something other than whitespace. Expand to word. - while (mSelX1 > 0 && !"".equals(screen.getSelectedText(mSelX1 - 1, mSelY1, mSelX1 - 1, mSelY1))) { - mSelX1--; - } - while (mSelX2 < mEmulator.mColumns - 1 && !"".equals(screen.getSelectedText(mSelX2 + 1, mSelY1, mSelX2 + 1, mSelY1))) { - mSelX2++; - } - } - - mInitialTextSelection = true; - mIsDraggingLeftSelection = true; - mSelectionDownX = ev.getX(); - mSelectionDownY = ev.getY(); - - final ActionMode.Callback callback = new ActionMode.Callback() { - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - int show = MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT; - - ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); - menu.add(Menu.NONE, 1, Menu.NONE, R.string.copy_text).setShowAsAction(show); - menu.add(Menu.NONE, 2, Menu.NONE, R.string.paste_text).setEnabled(clipboard.hasPrimaryClip()).setShowAsAction(show); - menu.add(Menu.NONE, 3, Menu.NONE, R.string.text_selection_more); - - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - if (!mIsSelectingText) { - // Fix issue where the dialog is pressed while being dismissed. - return true; - } - - switch (item.getItemId()) { - case 1: - String selectedText = mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2).trim(); - mTermSession.clipboardText(selectedText); - break; - case 2: - pasteFromClipboard(); - 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); + void sendTextToTerminal(CharSequence text) { + final int textLengthInChars = text.length(); + for (int i = 0; i < textLengthInChars; i++) { + char firstChar = text.charAt(i); + int codePoint; + if (Character.isHighSurrogate(firstChar)) { + if (++i < textLengthInChars) { + codePoint = Character.toCodePoint(firstChar, text.charAt(i)); } else { - mActionMode = startActionMode(callback); + // At end of string, with no low surrogate following the high: + codePoint = TerminalEmulator.UNICODE_REPLACEMENT_CHAR; + } + } else { + codePoint = firstChar; + } + + boolean ctrlHeld = false; + if (codePoint <= 31 && codePoint != 27) { + if (codePoint == '\n') { + // The AOSP keyboard and descendants seems to send \n as text when the enter key is pressed, + // instead of a key event like most other keyboard apps. A terminal expects \r for the enter + // key (although when icrnl is enabled this doesn't make a difference - run 'stty -icrnl' to + // check the behaviour). + codePoint = '\r'; } + // E.g. penti keyboard for ctrl input. + ctrlHeld = true; + switch (codePoint) { + case 31: + codePoint = '_'; + break; + case 30: + codePoint = '^'; + break; + case 29: + codePoint = ']'; + break; + case 28: + codePoint = '\\'; + break; + default: + codePoint += 96; + break; + } + } - invalidate(); - } else { - mActionMode.finish(); - mSelX1 = mSelY1 = mSelX2 = mSelY2 = -1; - invalidate(); + inputCodePoint(codePoint, ctrlHeld, false); } + } + + }; + } + + @Override + protected int computeVerticalScrollRange() { + return mEmulator == null ? 1 : mEmulator.getScreen().getActiveRows(); + } + + @Override + protected int computeVerticalScrollExtent() { + return mEmulator == null ? 1 : mEmulator.mRows; + } + + @Override + protected int computeVerticalScrollOffset() { + return mEmulator == null ? 1 : mEmulator.getScreen().getActiveRows() + mTopRow - mEmulator.mRows; + } + + public void onScreenUpdated() { + if (mEmulator == null) return; + boolean skipScrolling = false; + boolean isScreenHeld = false; + + // currentScroll 记录了当前滚动到的位置 + // expectedScroll 记录了假设一直跟随输出滚动在最底部时的滚动位置 + // 如果二者不一样,即 mTop != 0,则说明用户在脚本输出的时候滚动了屏幕 + // 很有可能时用户需要观察上面脚本的输出结果 + // 那么这个时候我们就不跟随输出滚动屏幕 + // int currentScroll = computeVerticalScrollOffset(); + // int expectedScroll = mEmulator.getScreen().getActiveRows() - mEmulator.mRows; + + if (mTopRow != 0) { + isScreenHeld = true; } - public TerminalSession getCurrentSession() { - return mTermSession; + if (mIsSelectingText || isScreenHeld) { + // Do not scroll when selecting text. + int rowsInHistory = mEmulator.getScreen().getActiveTranscriptRows(); + int rowShift = mEmulator.getScrollCounter(); + if (-mTopRow + rowShift > rowsInHistory) { + // .. unless we're hitting the end of history transcript, in which + // case we abort text selection and scroll to end. + + // 只当是因为选择文字而停止滚动时才取消选择文字 + if (mIsSelectingText) { + toggleSelectingText(null); + } + } else { + skipScrolling = true; + mTopRow -= rowShift; + mSelY1 -= rowShift; + mSelY2 -= rowShift; + } + + // 不滚动屏幕,但要让滚动条显示来告诉用户脚本在输出 + if (isScreenHeld) { + awakenScrollBars(); + } } - - private OnAutoCompleteListener onAutoCompleteListener; - - public OnAutoCompleteListener getOnAutoCompleteListener() { - return onAutoCompleteListener; + if (!skipScrolling && mTopRow != 0) { + // 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; } - public void setOnAutoCompleteListener(OnAutoCompleteListener onAutoCompleteListener) { - this.onAutoCompleteListener = onAutoCompleteListener; + mEmulator.clearScrollCounter(); + invalidate(); + + // Basic accessibility service + String contentText = mEmulator.getScreen() + .getSelectedText(0, mTopRow, mEmulator.mColumns, mTopRow + mEmulator.mRows); + if (mAccessibilityEnabled) { + setContentDescription(contentText); + } + } + + public int getTextSize() { + return mTextSize; + } + + /** + * Sets the text size, which in turn sets the number of rows and columns. + * + * @param textSize the new font size, in density-independent pixels. + */ + public void setTextSize(int textSize) { + this.mTextSize = textSize; + mRenderer = new TerminalRenderer(textSize, mRenderer == null ? Typeface.MONOSPACE : mRenderer.mTypeface); + updateSize(); + } + + public void setTypeface(Typeface newTypeface) { + mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface); + updateSize(); + invalidate(); + } + + @Override + public boolean onCheckIsTextEditor() { + return true; + } + + @Override + public boolean isOpaque() { + return true; + } + + /** + * Send a single mouse event code to the terminal. + */ + void sendMouseEventCode(MotionEvent e, int button, boolean pressed) { + int x = (int) (e.getX() / mRenderer.mFontWidth) + 1; + int y = (int) ((e.getY() - mRenderer.mFontLineSpacingAndAscent) / mRenderer.mFontLineSpacing) + 1; + if (pressed && (button == TerminalEmulator.MOUSE_WHEELDOWN_BUTTON || button == TerminalEmulator.MOUSE_WHEELUP_BUTTON)) { + if (mMouseStartDownTime == e.getDownTime()) { + x = mMouseScrollStartX; + y = mMouseScrollStartY; + } else { + mMouseStartDownTime = e.getDownTime(); + mMouseScrollStartX = x; + mMouseScrollStartY = y; + } + } + mEmulator.sendMouseEvent(button, x, y, pressed); + } + + /** + * Perform a scroll, either from dragging the screen or by scrolling a mouse wheel. + */ + void doScroll(MotionEvent event, int rowsDown) { + boolean up = rowsDown < 0; + int amount = Math.abs(rowsDown); + for (int i = 0; i < amount; i++) { + if (mEmulator.isMouseTrackingActive()) { + sendMouseEventCode(event, up ? TerminalEmulator.MOUSE_WHEELUP_BUTTON : TerminalEmulator.MOUSE_WHEELDOWN_BUTTON, true); + } else if (mEmulator.isAlternateBufferActive()) { + // Send up and down key events for scrolling, which is what some terminals do to make scroll work in + // e.g. less, which shifts to the alt screen without mouse handling. + handleKeyCode(up ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN, 0); + } else { + mTopRow = Math.min(0, Math.max(-(mEmulator.getScreen().getActiveTranscriptRows()), mTopRow + (up ? -1 : 1))); + if (!awakenScrollBars()) invalidate(); + } + } + } + + /** + * Overriding {@link View#onGenericMotionEvent(MotionEvent)}. + */ + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + if (mEmulator != null && event.isFromSource(InputDevice.SOURCE_MOUSE) && event.getAction() == MotionEvent.ACTION_SCROLL) { + // Handle mouse wheel scrolling. + boolean up = event.getAxisValue(MotionEvent.AXIS_VSCROLL) > 0.0f; + doScroll(event, up ? -3 : 3); + return true; + } + return false; + } + + @SuppressLint("ClickableViewAccessibility") + @Override + @TargetApi(23) + public boolean onTouchEvent(MotionEvent ev) { + if (mEmulator == null) return true; + final int action = ev.getAction(); + + if (mIsSelectingText) { + int cy = (int) (ev.getY() / mRenderer.mFontLineSpacing) + mTopRow; + int cx = (int) (ev.getX() / mRenderer.mFontWidth); + + switch (action) { + case MotionEvent.ACTION_UP: + mInitialTextSelection = false; + break; + case MotionEvent.ACTION_DOWN: + int distanceFromSel1 = Math.abs(cx - mSelX1) + Math.abs(cy - mSelY1); + int distanceFromSel2 = Math.abs(cx - mSelX2) + Math.abs(cy - mSelY2); + mIsDraggingLeftSelection = distanceFromSel1 <= distanceFromSel2; + mSelectionDownX = ev.getX(); + mSelectionDownY = ev.getY(); + break; + case MotionEvent.ACTION_MOVE: + if (mInitialTextSelection) break; + float deltaX = ev.getX() - mSelectionDownX; + float deltaY = ev.getY() - mSelectionDownY; + int deltaCols = (int) Math.ceil(deltaX / mRenderer.mFontWidth); + int deltaRows = (int) Math.ceil(deltaY / mRenderer.mFontLineSpacing); + mSelectionDownX += deltaCols * mRenderer.mFontWidth; + mSelectionDownY += deltaRows * mRenderer.mFontLineSpacing; + if (mIsDraggingLeftSelection) { + mSelX1 += deltaCols; + mSelY1 += deltaRows; + } else { + mSelX2 += deltaCols; + mSelY2 += deltaRows; + } + + mSelX1 = Math.min(mEmulator.mColumns, Math.max(0, mSelX1)); + mSelX2 = Math.min(mEmulator.mColumns, Math.max(0, mSelX2)); + + if (mSelY1 == mSelY2 && mSelX1 > mSelX2 || mSelY1 > mSelY2) { + // Switch handles. + mIsDraggingLeftSelection = !mIsDraggingLeftSelection; + int tmpX1 = mSelX1, tmpY1 = mSelY1; + mSelX1 = mSelX2; + mSelY1 = mSelY2; + mSelX2 = tmpX1; + mSelY2 = tmpY1; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + mActionMode.invalidateContentRect(); + invalidate(); + break; + default: + break; + } + mGestureRecognizer.onTouchEvent(ev); + return true; + } else if (ev.isFromSource(InputDevice.SOURCE_MOUSE)) { + if (ev.isButtonPressed(MotionEvent.BUTTON_SECONDARY)) { + if (action == MotionEvent.ACTION_DOWN) showContextMenu(); + return true; + } else if (ev.isButtonPressed(MotionEvent.BUTTON_TERTIARY)) { + pasteFromClipboard(); + } 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; + } } - public int getCursorAbsoluteX() { - return (int) mRenderer.getCursorX(); + mGestureRecognizer.onTouchEvent(ev); + return true; + } + + public void pasteFromClipboard() { + ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + if (clipboard == null) { + return; + } + ClipData clipData = clipboard.getPrimaryClip(); + if (clipData != null) { + CharSequence paste = clipData.getItemAt(0).coerceToText(getContext()); + if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString()); + } + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (LOG_KEY_EVENTS) + Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")"); + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (mIsSelectingText) { + toggleSelectingText(null); + return true; + } else if (mClient.shouldBackButtonBeMappedToEscape()) { + // Intercept back button to treat it as escape: + switch (event.getAction()) { + case KeyEvent.ACTION_DOWN: + return onKeyDown(keyCode, event); + case KeyEvent.ACTION_UP: + return onKeyUp(keyCode, event); + } + } + } + return super.onKeyPreIme(keyCode, event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (LOG_KEY_EVENTS) + Log.i(EmulatorDebug.LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")"); + if (mEmulator == null) return true; + + if (mClient.onKeyDown(keyCode, event, mTermSession)) { + invalidate(); + return true; + } else if (event.isSystem() && (!mClient.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) { + return super.onKeyDown(keyCode, event); + } else if (event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) { + mTermSession.write(event.getCharacters()); + return true; } - public int getCursorAbsoluteY() { - int[] locations = new int[2]; - getLocationOnScreen(locations); - return (int) (mRenderer.getCursorY() + locations[1]); + final int metaState = event.getMetaState(); + final boolean controlDownFromEvent = event.isCtrlPressed(); + final boolean leftAltDownFromEvent = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0; + final boolean rightAltDownFromEvent = (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0; + + int keyMod = 0; + if (controlDownFromEvent) keyMod |= KeyHandler.KEYMOD_CTRL; + if (event.isAltPressed()) keyMod |= KeyHandler.KEYMOD_ALT; + if (event.isShiftPressed()) keyMod |= KeyHandler.KEYMOD_SHIFT; + if (!event.isFunctionPressed() && handleKeyCode(keyCode, keyMod)) { + if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "handleKeyCode() took key event"); + return true; } - public void setEnableWordBasedIme(boolean mEnableWordBasedIme) { - this.mEnableWordBasedIme = mEnableWordBasedIme; + // Clear Ctrl since we handle that ourselves: + int bitsToClear = KeyEvent.META_CTRL_MASK; + if (rightAltDownFromEvent) { + // Let right Alt/Alt Gr be used to compose characters. + } else { + // Use left alt to send to terminal (e.g. Left Alt+B to jump back a word), so remove: + bitsToClear |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON; } + int effectiveMetaState = event.getMetaState() & ~bitsToClear; + + int result = event.getUnicodeChar(effectiveMetaState); + if (LOG_KEY_EVENTS) + Log.i(EmulatorDebug.LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result); + if (result == 0) { + return false; + } + + int oldCombiningAccent = mCombiningAccent; + if ((result & KeyCharacterMap.COMBINING_ACCENT) != 0) { + // If entered combining accent previously, write it out: + if (mCombiningAccent != 0) + inputCodePoint(mCombiningAccent, controlDownFromEvent, leftAltDownFromEvent); + mCombiningAccent = result & KeyCharacterMap.COMBINING_ACCENT_MASK; + } else { + if (mCombiningAccent != 0) { + int combinedChar = KeyCharacterMap.getDeadChar(mCombiningAccent, result); + if (combinedChar > 0) result = combinedChar; + mCombiningAccent = 0; + } + inputCodePoint(result, controlDownFromEvent, leftAltDownFromEvent); + } + + if (mCombiningAccent != oldCombiningAccent) invalidate(); + + if (onAutoCompleteListener != null) { + if (event.isPrintingKey()) { + char printingChar = (char) event.getUnicodeChar(metaState); + if (printingChar != '\b') { + // ASCII chars + onAutoCompleteListener.onCompletionRequired(new String(new char[]{printingChar})); + } + } + } + + return true; + } + + void inputCodePoint(int codePoint, boolean controlDownFromEvent, boolean leftAltDownFromEvent) { + if (LOG_KEY_EVENTS) { + Log.i(EmulatorDebug.LOG_TAG, "inputCodePoint(codePoint=" + codePoint + ", controlDownFromEvent=" + controlDownFromEvent + ", leftAltDownFromEvent=" + + leftAltDownFromEvent + ")"); + } + + if (mTermSession == null) return; + + final boolean controlDown = controlDownFromEvent || mClient.readControlKey(); + final boolean altDown = leftAltDownFromEvent || mClient.readAltKey(); + + if (mClient.onCodePoint(codePoint, controlDown, mTermSession)) return; + + if (controlDown) { + if (codePoint >= 'a' && codePoint <= 'z') { + codePoint = codePoint - 'a' + 1; + } else if (codePoint >= 'A' && codePoint <= 'Z') { + codePoint = codePoint - 'A' + 1; + } else if (codePoint == ' ' || codePoint == '2') { + codePoint = 0; + } else if (codePoint == '[' || codePoint == '3') { + codePoint = 27; // ^[ (Esc) + } else if (codePoint == '\\' || codePoint == '4') { + codePoint = 28; + } else if (codePoint == ']' || codePoint == '5') { + codePoint = 29; + } else if (codePoint == '^' || codePoint == '6') { + codePoint = 30; // control-^ + } else if (codePoint == '_' || codePoint == '7' || codePoint == '/') { + // "Ctrl-/ sends 0x1f which is equivalent of Ctrl-_ since the days of VT102" + // - http://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal + codePoint = 31; + } else if (codePoint == '8') { + codePoint = 127; // DEL + } + } + + if (codePoint > -1) { + // Work around bluetooth keyboards sending funny unicode characters instead + // of the more normal ones from ASCII that terminal programs expect - the + // desire to input the original characters should be low. + switch (codePoint) { + case 0x02DC: // SMALL TILDE. + codePoint = 0x007E; // TILDE (~). + 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: + mTermSession.writeCodePoint(altDown, codePoint); + scrollToBottomIfNeeded(); + } + } + + /** + * Input the specified keyCode if applicable and return if the input was consumed. + */ + public boolean handleKeyCode(int keyCode, int keyMod) { + TerminalEmulator term = mTermSession.getEmulator(); + String code = KeyHandler.getCode(keyCode, keyMod, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode()); + if (code == null) return false; + mTermSession.write(code); + scrollToBottomIfNeeded(); + if (onAutoCompleteListener != null) { + onAutoCompleteListener.onKeyCode(keyCode, keyMod); + } + return true; + } + + /** + * Called when a key is released in the view. + * + * @param keyCode The keycode of the key which was released. + * @param event A {@link KeyEvent} describing the event. + * @return Whether the event was handled. + */ + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (LOG_KEY_EVENTS) + Log.i(EmulatorDebug.LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")"); + if (mEmulator == null) return true; + + if (mClient.onKeyUp(keyCode, event)) { + invalidate(); + return true; + } else if (event.isSystem()) { + // Let system key events through. + return super.onKeyUp(keyCode, event); + } + + return true; + } + + /** + * 每次处理用户按下对终端输出有影响的键时被调用 + * 如果当前屏幕不处于最底部,则自动滚动到最底部 + */ + void scrollToBottomIfNeeded() { + if (mTopRow != 0) { + mTopRow = 0; + mEmulator.clearScrollCounter(); + invalidate(); + } + } + + /** + * This is called during layout when the size of this view has changed. If you were just added to the view + * hierarchy, you're called with the old values of 0. + */ + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + updateSize(); + } + + /** + * Check if the terminal size in rows and columns should be updated. + */ + public void updateSize() { + int viewWidth = getWidth(); + int viewHeight = getHeight(); + if (viewWidth == 0 || viewHeight == 0 || mTermSession == null) return; + + // Set to 80 and 24 if you want to enable vttest. + int newColumns = Math.max(4, (int) (viewWidth / mRenderer.mFontWidth)); + int newRows = Math.max(4, (viewHeight - mRenderer.mFontLineSpacingAndAscent) / mRenderer.mFontLineSpacing); + + if (mEmulator == null || (newColumns != mEmulator.mColumns || newRows != mEmulator.mRows)) { + mTermSession.updateSize(newColumns, newRows); + mEmulator = mTermSession.getEmulator(); + + mTopRow = 0; + scrollTo(0, 0); + invalidate(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (mEmulator == null) { + canvas.drawColor(0XFF000000); + } else { + 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. + */ + @TargetApi(23) + public void toggleSelectingText(MotionEvent ev) { + mIsSelectingText = !mIsSelectingText; + mClient.copyModeChanged(mIsSelectingText); + + if (mIsSelectingText) { + if (mLeftSelectionHandle == null) { + mLeftSelectionHandle = (BitmapDrawable) getContext().getDrawable(R.drawable.text_select_handle_left_material); + mRightSelectionHandle = (BitmapDrawable) getContext().getDrawable(R.drawable.text_select_handle_right_material); + } + + int cx = (int) (ev.getX() / mRenderer.mFontWidth); + final boolean eventFromMouse = ev.isFromSource(InputDevice.SOURCE_MOUSE); + // Offset for finger: + final int SELECT_TEXT_OFFSET_Y = eventFromMouse ? 0 : -40; + int cy = (int) ((ev.getY() + SELECT_TEXT_OFFSET_Y) / mRenderer.mFontLineSpacing) + mTopRow; + + mSelX1 = mSelX2 = cx; + mSelY1 = mSelY2 = cy; + + TerminalBuffer screen = mEmulator.getScreen(); + if (!" ".equals(screen.getSelectedText(mSelX1, mSelY1, mSelX1, mSelY1))) { + // Selecting something other than whitespace. Expand to word. + while (mSelX1 > 0 && !"".equals(screen.getSelectedText(mSelX1 - 1, mSelY1, mSelX1 - 1, mSelY1))) { + mSelX1--; + } + while (mSelX2 < mEmulator.mColumns - 1 && !"".equals(screen.getSelectedText(mSelX2 + 1, mSelY1, mSelX2 + 1, mSelY1))) { + mSelX2++; + } + } + + mInitialTextSelection = true; + mIsDraggingLeftSelection = true; + mSelectionDownX = ev.getX(); + mSelectionDownY = ev.getY(); + + final ActionMode.Callback callback = new ActionMode.Callback() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + int show = MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT; + + ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + menu.add(Menu.NONE, 1, Menu.NONE, R.string.copy_text).setShowAsAction(show); + menu.add(Menu.NONE, 2, Menu.NONE, R.string.paste_text).setEnabled(clipboard.hasPrimaryClip()).setShowAsAction(show); + menu.add(Menu.NONE, 3, Menu.NONE, R.string.text_selection_more); + + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + if (!mIsSelectingText) { + // Fix issue where the dialog is pressed while being dismissed. + return true; + } + + switch (item.getItemId()) { + case 1: + String selectedText = mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2).trim(); + mTermSession.clipboardText(selectedText); + break; + case 2: + pasteFromClipboard(); + 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() { + return mTermSession; + } + + + private OnAutoCompleteListener onAutoCompleteListener; + + public OnAutoCompleteListener getOnAutoCompleteListener() { + return onAutoCompleteListener; + } + + public void setOnAutoCompleteListener(OnAutoCompleteListener onAutoCompleteListener) { + this.onAutoCompleteListener = onAutoCompleteListener; + } + + public int getCursorAbsoluteX() { + return (int) mRenderer.getCursorX(); + } + + public int getCursorAbsoluteY() { + int[] locations = new int[2]; + getLocationOnScreen(locations); + return (int) (mRenderer.getCursorY() + locations[1]); + } + + public void setEnableWordBasedIme(boolean mEnableWordBasedIme) { + this.mEnableWordBasedIme = mEnableWordBasedIme; + } } diff --git a/app/src/main/java/io/neoterm/frontend/terminal/TerminalViewClient.java b/app/src/main/java/io/neoterm/frontend/terminal/TerminalViewClient.java index 82ba3db..b5a6eb1 100755 --- a/app/src/main/java/io/neoterm/frontend/terminal/TerminalViewClient.java +++ b/app/src/main/java/io/neoterm/frontend/terminal/TerminalViewClient.java @@ -3,7 +3,6 @@ package io.neoterm.frontend.terminal; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ScaleGestureDetector; - import io.neoterm.backend.TerminalSession; /** @@ -13,30 +12,30 @@ import io.neoterm.backend.TerminalSession; */ public interface TerminalViewClient { - /** - * Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}. - */ - float onScale(float scale); + /** + * Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}. + */ + float onScale(float scale); - /** - * On a single tap on the terminal if terminal mouse reporting not enabled. - */ - void onSingleTapUp(MotionEvent e); + /** + * On a single tap on the terminal if terminal mouse reporting not enabled. + */ + void onSingleTapUp(MotionEvent e); - boolean shouldBackButtonBeMappedToEscape(); + boolean shouldBackButtonBeMappedToEscape(); - void copyModeChanged(boolean copyMode); + void copyModeChanged(boolean copyMode); - boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session); + boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session); - boolean onKeyUp(int keyCode, KeyEvent e); + boolean onKeyUp(int keyCode, KeyEvent e); - boolean readControlKey(); + boolean readControlKey(); - boolean readAltKey(); + boolean readAltKey(); - boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session); + boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session); - boolean onLongPress(MotionEvent event); + boolean onLongPress(MotionEvent event); } diff --git a/app/src/main/java/io/neoterm/frontend/terminal/extrakey/ExtraKeysView.kt b/app/src/main/java/io/neoterm/frontend/terminal/extrakey/ExtraKeysView.kt index 2fcf3c3..ee22fe0 100755 --- a/app/src/main/java/io/neoterm/frontend/terminal/extrakey/ExtraKeysView.kt +++ b/app/src/main/java/io/neoterm/frontend/terminal/extrakey/ExtraKeysView.kt @@ -22,220 +22,222 @@ import java.io.File class ExtraKeysView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { - companion object { - private val ESC = ControlButton(IExtraButton.KEY_ESC) - private val TAB = ControlButton(IExtraButton.KEY_TAB) - private val PAGE_UP = RepeatableButton(IExtraButton.KEY_PAGE_UP) - private val PAGE_DOWN = RepeatableButton(IExtraButton.KEY_PAGE_DOWN) - private val HOME = ControlButton(IExtraButton.KEY_HOME) - private val END = ControlButton(IExtraButton.KEY_END) - private val ARROW_UP = ArrowButton(IExtraButton.KEY_ARROW_UP) - private val ARROW_DOWN = ArrowButton(IExtraButton.KEY_ARROW_DOWN) - private val ARROW_LEFT = ArrowButton(IExtraButton.KEY_ARROW_LEFT) - private val ARROW_RIGHT = ArrowButton(IExtraButton.KEY_ARROW_RIGHT) - private val TOGGLE_IME = object : ControlButton(IExtraButton.KEY_TOGGLE_IME) { - override fun onClick(view: View) { - EventBus.getDefault().post(ToggleImeEvent()) - } - } - - private val MAX_BUTTONS_PER_LINE = 7 - private val DEFAULT_ALPHA = 0.8f - private val EXPANDED_ALPHA = 0.5f - private val USER_KEYS_BUTTON_LINE_START = 2 + companion object { + private val ESC = ControlButton(IExtraButton.KEY_ESC) + private val TAB = ControlButton(IExtraButton.KEY_TAB) + private val PAGE_UP = RepeatableButton(IExtraButton.KEY_PAGE_UP) + private val PAGE_DOWN = RepeatableButton(IExtraButton.KEY_PAGE_DOWN) + private val HOME = ControlButton(IExtraButton.KEY_HOME) + private val END = ControlButton(IExtraButton.KEY_END) + private val ARROW_UP = ArrowButton(IExtraButton.KEY_ARROW_UP) + private val ARROW_DOWN = ArrowButton(IExtraButton.KEY_ARROW_DOWN) + private val ARROW_LEFT = ArrowButton(IExtraButton.KEY_ARROW_LEFT) + private val ARROW_RIGHT = ArrowButton(IExtraButton.KEY_ARROW_RIGHT) + private val TOGGLE_IME = object : ControlButton(IExtraButton.KEY_TOGGLE_IME) { + override fun onClick(view: View) { + EventBus.getDefault().post(ToggleImeEvent()) + } } - private val builtinKeys = mutableListOf() - private val userKeys = mutableListOf() + private val MAX_BUTTONS_PER_LINE = 7 + private val DEFAULT_ALPHA = 0.8f + private val EXPANDED_ALPHA = 0.5f + private val USER_KEYS_BUTTON_LINE_START = 2 + } - private val buttonBars: MutableList = mutableListOf() - private var typeface: Typeface? = null + private val builtinKeys = mutableListOf() + private val userKeys = mutableListOf() - // Initialize StatedControlButton here - // For avoid memory and context leak. - private val CTRL = StatedControlButton(IExtraButton.KEY_CTRL) - private val ALT = StatedControlButton(IExtraButton.KEY_ALT) + private val buttonBars: MutableList = mutableListOf() + private var typeface: Typeface? = null - private var buttonPanelExpanded = false - private val EXPAND_BUTTONS = object : ControlButton(IExtraButton.KEY_SHOW_ALL_BUTTONS) { - override fun onClick(view: View) { - expandButtonPanel() - } + // Initialize StatedControlButton here + // For avoid memory and context leak. + private val CTRL = StatedControlButton(IExtraButton.KEY_CTRL) + private val ALT = StatedControlButton(IExtraButton.KEY_ALT) + + private var buttonPanelExpanded = false + private val EXPAND_BUTTONS = object : ControlButton(IExtraButton.KEY_SHOW_ALL_BUTTONS) { + override fun onClick(view: View) { + expandButtonPanel() + } + } + + private val extraKeyComponent: ExtraKeyComponent + + init { + alpha = DEFAULT_ALPHA + gravity = Gravity.TOP + orientation = LinearLayout.VERTICAL + typeface = Typeface.createFromAsset(context.assets, "eks_font.ttf") + extraKeyComponent = ComponentManager.getComponent() + + initBuiltinKeys() + loadDefaultUserKeys() + updateButtons() + expandButtonPanel(false) + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + if (keyCode == KeyEvent.KEYCODE_BACK && event?.action == KeyEvent.ACTION_DOWN) { + if (buttonPanelExpanded) { + expandButtonPanel() + return true + } + return false + } + return super.onKeyDown(keyCode, event) + } + + fun setTextColor(textColor: Int) { + IExtraButton.NORMAL_TEXT_COLOR = textColor + updateButtons() + } + + fun setTypeface(typeface: Typeface?) { + this.typeface = typeface + updateButtons() + } + + fun readControlButton(): Boolean { + return CTRL.readState() + } + + fun readAltButton(): Boolean { + return ALT.readState() + } + + fun addUserKey(button: IExtraButton) { + addKeyButton(userKeys, button) + } + + fun addBuiltinKey(button: IExtraButton) { + addKeyButton(builtinKeys, button) + } + + fun clearUserKeys() { + userKeys.clear() + } + + fun loadDefaultUserKeys() { + clearUserKeys() + val defaultConfig = extraKeyComponent.loadConfigure(File(NeoTermPath.EKS_DEFAULT_FILE)) + if (defaultConfig != null) { + userKeys.addAll(defaultConfig.shortcutKeys) + } + } + + fun updateButtons() { + buttonBars.forEach { it.removeAllViews() } + + var targetButtonBarIndex = 0 + builtinKeys.plus(userKeys).forEachIndexed { index, button -> + addKeyButton(getButtonBarOrNew(targetButtonBarIndex), button) + targetButtonBarIndex = (index + 1) / MAX_BUTTONS_PER_LINE + } + updateButtonBars() + } + + private fun updateButtonBars() { + removeAllViews() + + buttonBars.asReversed() + .forEach { addView(it) } + } + + private fun expandButtonPanel(forceSetExpanded: Boolean? = null) { + if (buttonBars.size <= 2) { + return } - private val extraKeyComponent: ExtraKeyComponent + buttonPanelExpanded = forceSetExpanded ?: !buttonPanelExpanded + val visibility = if (buttonPanelExpanded) View.VISIBLE else View.GONE + alpha = if (buttonPanelExpanded) EXPANDED_ALPHA else DEFAULT_ALPHA - init { - alpha = DEFAULT_ALPHA - gravity = Gravity.TOP - orientation = LinearLayout.VERTICAL - typeface = Typeface.createFromAsset(context.assets, "eks_font.ttf") - extraKeyComponent = ComponentManager.getComponent() + IntRange(USER_KEYS_BUTTON_LINE_START, buttonBars.size - 1) + .map { buttonBars[it] } + .forEach { it.visibility = visibility } + } - initBuiltinKeys() - loadDefaultUserKeys() - updateButtons() - expandButtonPanel(false) + private fun createNewButtonBar(): LinearLayout { + val line = LinearLayout(context) + + val layoutParams = + if (NeoPreference.isExplicitExtraKeysWeightEnabled()) + LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f) + else + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + + layoutParams.setMargins(0, 0, 0, 0) + line.setPadding(0, 0, 0, 0) + line.gravity = Gravity.START + line.orientation = LinearLayout.HORIZONTAL + line.layoutParams = layoutParams + return line + } + + private fun getButtonBarOrNew(position: Int): LinearLayout { + if (position >= buttonBars.size) { + for (i in 0..(position - buttonBars.size + 1)) { + buttonBars.add(createNewButtonBar()) + } } + return buttonBars[position] + } - override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { - if (keyCode == KeyEvent.KEYCODE_BACK && event?.action == KeyEvent.ACTION_DOWN) { - if (buttonPanelExpanded) { - expandButtonPanel() - return true - } - return false - } - return super.onKeyDown(keyCode, event) + private fun addKeyButton(buttons: MutableList?, button: IExtraButton) { + if (buttons != null && !buttons.contains(button)) { + buttons.add(button) } + } - fun setTextColor(textColor: Int) { - IExtraButton.NORMAL_TEXT_COLOR = textColor - updateButtons() + private fun addKeyButton(contentView: LinearLayout, extraButton: IExtraButton) { + val outerButton = extraButton.makeButton(context, null, android.R.attr.buttonBarButtonStyle) + + val param = GridLayout.LayoutParams() + param.setGravity(Gravity.CENTER) + param.width = calculateButtonWidth() + param.height = context.resources.getDimensionPixelSize(R.dimen.eks_height) + param.setMargins(0, 0, 0, 0) + + outerButton.layoutParams = param + outerButton.maxLines = 1 + outerButton.typeface = typeface + outerButton.text = extraButton.displayText + outerButton.setPadding(0, 0, 0, 0) + outerButton.setTextColor(IExtraButton.NORMAL_TEXT_COLOR) + outerButton.setAllCaps(false) + + outerButton.setOnClickListener { + outerButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + val root = rootView + extraButton.onClick(root) } + contentView.addView(outerButton) + } - fun setTypeface(typeface: Typeface?) { - this.typeface = typeface - updateButtons() - } + private fun initBuiltinKeys() { + addBuiltinKey(ESC) + addBuiltinKey(TAB) + addBuiltinKey(PAGE_DOWN) + addBuiltinKey(ARROW_LEFT) + addBuiltinKey(ARROW_DOWN) + addBuiltinKey(ARROW_RIGHT) + addBuiltinKey(TOGGLE_IME) - fun readControlButton(): Boolean { - return CTRL.readState() - } + addBuiltinKey(CTRL) + addBuiltinKey(ALT) + addBuiltinKey(PAGE_UP) + addBuiltinKey(HOME) + addBuiltinKey(ARROW_UP) + addBuiltinKey(END) + addBuiltinKey(EXPAND_BUTTONS) + } - fun readAltButton(): Boolean { - return ALT.readState() - } - - fun addUserKey(button: IExtraButton) { - addKeyButton(userKeys, button) - } - - fun addBuiltinKey(button: IExtraButton) { - addKeyButton(builtinKeys, button) - } - - fun clearUserKeys() { - userKeys.clear() - } - - fun loadDefaultUserKeys() { - clearUserKeys() - val defaultConfig = extraKeyComponent.loadConfigure(File(NeoTermPath.EKS_DEFAULT_FILE)) - if (defaultConfig != null) { - userKeys.addAll(defaultConfig.shortcutKeys) - } - } - - fun updateButtons() { - buttonBars.forEach { it.removeAllViews() } - - var targetButtonBarIndex = 0 - builtinKeys.plus(userKeys).forEachIndexed { index, button -> - addKeyButton(getButtonBarOrNew(targetButtonBarIndex), button) - targetButtonBarIndex = (index + 1) / MAX_BUTTONS_PER_LINE - } - updateButtonBars() - } - - private fun updateButtonBars() { - removeAllViews() - - buttonBars.asReversed() - .forEach { addView(it) } - } - - private fun expandButtonPanel(forceSetExpanded: Boolean? = null) { - if (buttonBars.size <= 2) { - return - } - - buttonPanelExpanded = forceSetExpanded ?: !buttonPanelExpanded - val visibility = if (buttonPanelExpanded) View.VISIBLE else View.GONE - alpha = if (buttonPanelExpanded) EXPANDED_ALPHA else DEFAULT_ALPHA - - IntRange(USER_KEYS_BUTTON_LINE_START, buttonBars.size - 1) - .map { buttonBars[it] } - .forEach { it.visibility = visibility } - } - - private fun createNewButtonBar(): LinearLayout { - val line = LinearLayout(context) - - val layoutParams = - if (NeoPreference.isExplicitExtraKeysWeightEnabled()) - LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f) - else - LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT) - - layoutParams.setMargins(0, 0, 0, 0) - line.setPadding(0, 0, 0, 0) - line.gravity = Gravity.START - line.orientation = LinearLayout.HORIZONTAL - line.layoutParams = layoutParams - return line - } - - private fun getButtonBarOrNew(position: Int): LinearLayout { - if (position >= buttonBars.size) { - for (i in 0..(position - buttonBars.size + 1)) { - buttonBars.add(createNewButtonBar()) - } - } - return buttonBars[position] - } - - private fun addKeyButton(buttons: MutableList?, button: IExtraButton) { - if (buttons != null && !buttons.contains(button)) { - buttons.add(button) - } - } - - private fun addKeyButton(contentView: LinearLayout, extraButton: IExtraButton) { - val outerButton = extraButton.makeButton(context, null, android.R.attr.buttonBarButtonStyle) - - val param = GridLayout.LayoutParams() - param.setGravity(Gravity.CENTER) - param.width = calculateButtonWidth() - param.height = context.resources.getDimensionPixelSize(R.dimen.eks_height) - param.setMargins(0, 0, 0, 0) - - outerButton.layoutParams = param - outerButton.maxLines = 1 - outerButton.typeface = typeface - outerButton.text = extraButton.displayText - outerButton.setPadding(0, 0, 0, 0) - outerButton.setTextColor(IExtraButton.NORMAL_TEXT_COLOR) - outerButton.setAllCaps(false) - - outerButton.setOnClickListener { - outerButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) - val root = rootView - extraButton.onClick(root) - } - contentView.addView(outerButton) - } - - private fun initBuiltinKeys() { - addBuiltinKey(ESC) - addBuiltinKey(TAB) - addBuiltinKey(PAGE_DOWN) - addBuiltinKey(ARROW_LEFT) - addBuiltinKey(ARROW_DOWN) - addBuiltinKey(ARROW_RIGHT) - addBuiltinKey(TOGGLE_IME) - - addBuiltinKey(CTRL) - addBuiltinKey(ALT) - addBuiltinKey(PAGE_UP) - addBuiltinKey(HOME) - addBuiltinKey(ARROW_UP) - addBuiltinKey(END) - addBuiltinKey(EXPAND_BUTTONS) - } - - private fun calculateButtonWidth(): Int { - return context.resources.displayMetrics.widthPixels / ExtraKeysView.MAX_BUTTONS_PER_LINE - } + private fun calculateButtonWidth(): Int { + return context.resources.displayMetrics.widthPixels / ExtraKeysView.MAX_BUTTONS_PER_LINE + } } diff --git a/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/IExtraButton.kt b/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/IExtraButton.kt index e7a0635..1a0b1dd 100644 --- a/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/IExtraButton.kt +++ b/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/IExtraButton.kt @@ -15,71 +15,71 @@ import io.neoterm.frontend.terminal.extrakey.combine.CombinedSequence abstract class IExtraButton : View.OnClickListener { - var buttonKeys: CombinedSequence? = null - var displayText: String? = null + var buttonKeys: CombinedSequence? = null + var displayText: String? = null - override fun toString(): String { - return "${this.javaClass.simpleName} { display: $displayText, code: ${buttonKeys?.keys} }" - } - - abstract override fun onClick(view: View) - - abstract fun makeButton(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): Button - - companion object { - val KEY_ESC = "Esc" - val KEY_TAB = "Tab" - val KEY_CTRL = "Ctrl" - val KEY_ALT = "Alt" - val KEY_PAGE_UP = "PgUp" - val KEY_PAGE_DOWN = "PgDn" - val KEY_HOME = "Home" - val KEY_END = "End" - val KEY_ARROW_UP_TEXT = "Up" - val KEY_ARROW_DOWN_TEXT = "Down" - val KEY_ARROW_LEFT_TEXT = "Left" - val KEY_ARROW_RIGHT_TEXT = "Right" - val KEY_SHOW_ALL_BUTTONS = "···" - val KEY_TOGGLE_IME = "Im" - - val KEY_ARROW_UP = "▲" - val KEY_ARROW_DOWN = "▼" - val KEY_ARROW_LEFT = "◀" - val KEY_ARROW_RIGHT = "▶" - - var NORMAL_TEXT_COLOR = 0xFFFFFFFF.toInt() - var SELECTED_TEXT_COLOR = 0xFF80DEEA.toInt() - - fun sendKey(view: View, keyName: String) { - var keyCode = 0 - var chars = "" - when (keyName) { - KEY_ESC -> keyCode = KeyEvent.KEYCODE_ESCAPE - KEY_TAB -> keyCode = KeyEvent.KEYCODE_TAB - KEY_ARROW_UP -> keyCode = KeyEvent.KEYCODE_DPAD_UP - KEY_ARROW_LEFT -> keyCode = KeyEvent.KEYCODE_DPAD_LEFT - KEY_ARROW_RIGHT -> keyCode = KeyEvent.KEYCODE_DPAD_RIGHT - KEY_ARROW_DOWN -> keyCode = KeyEvent.KEYCODE_DPAD_DOWN - KEY_ARROW_UP_TEXT -> keyCode = KeyEvent.KEYCODE_DPAD_UP - KEY_ARROW_LEFT_TEXT -> keyCode = KeyEvent.KEYCODE_DPAD_LEFT - KEY_ARROW_RIGHT_TEXT -> keyCode = KeyEvent.KEYCODE_DPAD_RIGHT - KEY_ARROW_DOWN_TEXT -> keyCode = KeyEvent.KEYCODE_DPAD_DOWN - KEY_PAGE_UP -> keyCode = KeyEvent.KEYCODE_PAGE_UP - KEY_PAGE_DOWN -> keyCode = KeyEvent.KEYCODE_PAGE_DOWN - KEY_HOME -> keyCode = KeyEvent.KEYCODE_MOVE_HOME - KEY_END -> keyCode = KeyEvent.KEYCODE_MOVE_END - "―" -> chars = "-" - else -> chars = keyName - } - - if (keyCode > 0) { - view.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, keyCode)) - view.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, keyCode)) - } else if (chars.isNotEmpty()) { - val terminalView = view.findViewById(R.id.terminal_view) - val session = terminalView.currentSession - session?.write(chars) - } - } + override fun toString(): String { + return "${this.javaClass.simpleName} { display: $displayText, code: ${buttonKeys?.keys} }" + } + + abstract override fun onClick(view: View) + + abstract fun makeButton(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): Button + + companion object { + val KEY_ESC = "Esc" + val KEY_TAB = "Tab" + val KEY_CTRL = "Ctrl" + val KEY_ALT = "Alt" + val KEY_PAGE_UP = "PgUp" + val KEY_PAGE_DOWN = "PgDn" + val KEY_HOME = "Home" + val KEY_END = "End" + val KEY_ARROW_UP_TEXT = "Up" + val KEY_ARROW_DOWN_TEXT = "Down" + val KEY_ARROW_LEFT_TEXT = "Left" + val KEY_ARROW_RIGHT_TEXT = "Right" + val KEY_SHOW_ALL_BUTTONS = "···" + val KEY_TOGGLE_IME = "Im" + + val KEY_ARROW_UP = "▲" + val KEY_ARROW_DOWN = "▼" + val KEY_ARROW_LEFT = "◀" + val KEY_ARROW_RIGHT = "▶" + + var NORMAL_TEXT_COLOR = 0xFFFFFFFF.toInt() + var SELECTED_TEXT_COLOR = 0xFF80DEEA.toInt() + + fun sendKey(view: View, keyName: String) { + var keyCode = 0 + var chars = "" + when (keyName) { + KEY_ESC -> keyCode = KeyEvent.KEYCODE_ESCAPE + KEY_TAB -> keyCode = KeyEvent.KEYCODE_TAB + KEY_ARROW_UP -> keyCode = KeyEvent.KEYCODE_DPAD_UP + KEY_ARROW_LEFT -> keyCode = KeyEvent.KEYCODE_DPAD_LEFT + KEY_ARROW_RIGHT -> keyCode = KeyEvent.KEYCODE_DPAD_RIGHT + KEY_ARROW_DOWN -> keyCode = KeyEvent.KEYCODE_DPAD_DOWN + KEY_ARROW_UP_TEXT -> keyCode = KeyEvent.KEYCODE_DPAD_UP + KEY_ARROW_LEFT_TEXT -> keyCode = KeyEvent.KEYCODE_DPAD_LEFT + KEY_ARROW_RIGHT_TEXT -> keyCode = KeyEvent.KEYCODE_DPAD_RIGHT + KEY_ARROW_DOWN_TEXT -> keyCode = KeyEvent.KEYCODE_DPAD_DOWN + KEY_PAGE_UP -> keyCode = KeyEvent.KEYCODE_PAGE_UP + KEY_PAGE_DOWN -> keyCode = KeyEvent.KEYCODE_PAGE_DOWN + KEY_HOME -> keyCode = KeyEvent.KEYCODE_MOVE_HOME + KEY_END -> keyCode = KeyEvent.KEYCODE_MOVE_END + "―" -> chars = "-" + else -> chars = keyName + } + + if (keyCode > 0) { + view.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, keyCode)) + view.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, keyCode)) + } else if (chars.isNotEmpty()) { + val terminalView = view.findViewById(R.id.terminal_view) + val session = terminalView.currentSession + session?.write(chars) + } } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/RepeatableButton.kt b/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/RepeatableButton.kt index 1b88c0c..302a58e 100644 --- a/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/RepeatableButton.kt +++ b/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/RepeatableButton.kt @@ -3,10 +3,10 @@ package io.neoterm.frontend.terminal.extrakey.button import android.content.Context import android.os.Handler import android.os.Looper -import androidx.appcompat.widget.AppCompatButton import android.util.AttributeSet import android.view.MotionEvent import android.widget.Button +import androidx.appcompat.widget.AppCompatButton /** @@ -14,46 +14,46 @@ import android.widget.Button */ open class RepeatableButton(buttonText: String) : ControlButton(buttonText) { - override fun makeButton(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): Button { - return RepeatableButtonWidget(context, attrs, defStyleAttr) + override fun makeButton(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): Button { + return RepeatableButtonWidget(context, attrs, defStyleAttr) + } + + private class RepeatableButtonWidget(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : + AppCompatButton(context!!, attrs, defStyleAttr) { + + /** + * Milliseconds how long we trigger an action + * when long pressing + */ + private val LONG_CLICK_ACTION_INTERVAL = 100L + + private var isMotionEventUp = true + + var mHandler: Handler = object : Handler(Looper.getMainLooper()) { + override fun handleMessage(msg: android.os.Message) { + if (!isMotionEventUp && isEnabled) { + performClick() + this.sendEmptyMessageDelayed(0, LONG_CLICK_ACTION_INTERVAL) + } + } } - private class RepeatableButtonWidget(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) - : AppCompatButton(context!!, attrs, defStyleAttr) { - - /** - * Milliseconds how long we trigger an action - * when long pressing - */ - private val LONG_CLICK_ACTION_INTERVAL = 100L - - private var isMotionEventUp = true - - var mHandler: Handler = object : Handler(Looper.getMainLooper()) { - override fun handleMessage(msg: android.os.Message) { - if (!isMotionEventUp && isEnabled) { - performClick() - this.sendEmptyMessageDelayed(0, LONG_CLICK_ACTION_INTERVAL) - } - } - } - - init { - this.setOnLongClickListener { - isMotionEventUp = false - mHandler.sendEmptyMessage(0) - false - } - this.setOnTouchListener { _, event -> - if (event.action == MotionEvent.ACTION_UP) { - isMotionEventUp = true - } - false - } - } - - override fun performClick(): Boolean { - return super.performClick() + init { + this.setOnLongClickListener { + isMotionEventUp = false + mHandler.sendEmptyMessage(0) + false + } + this.setOnTouchListener { _, event -> + if (event.action == MotionEvent.ACTION_UP) { + isMotionEventUp = true } + false + } } + + override fun performClick(): Boolean { + return super.performClick() + } + } } diff --git a/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/StatedControlButton.kt b/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/StatedControlButton.kt index 38109a8..f5e1701 100644 --- a/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/StatedControlButton.kt +++ b/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/StatedControlButton.kt @@ -10,46 +10,47 @@ import android.widget.ToggleButton * @author kiva */ -open class StatedControlButton @JvmOverloads constructor(text: String, var initState: Boolean = false) : ControlButton(text) { - var toggleButton: ToggleButton? = null +open class StatedControlButton @JvmOverloads constructor(text: String, var initState: Boolean = false) : + ControlButton(text) { + var toggleButton: ToggleButton? = null - override fun onClick(view: View) { - setStatus(toggleButton?.isChecked) + override fun onClick(view: View) { + setStatus(toggleButton?.isChecked) + } + + override fun makeButton(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): Button { + val outerButton = ToggleButton(context, null, android.R.attr.buttonBarButtonStyle) + + outerButton.isClickable = true + if (initState) { + outerButton.isChecked = true + outerButton.setTextColor(IExtraButton.SELECTED_TEXT_COLOR) } - override fun makeButton(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): Button { - val outerButton = ToggleButton(context, null, android.R.attr.buttonBarButtonStyle) + this.toggleButton = outerButton + return outerButton + } - outerButton.isClickable = true - if (initState) { - outerButton.isChecked = true - outerButton.setTextColor(IExtraButton.SELECTED_TEXT_COLOR) - } - - this.toggleButton = outerButton - return outerButton + private fun setStatus(status: Boolean?) { + val button = toggleButton + if (button != null && status != null) { + button.isChecked = status + button.setTextColor( + if (status) SELECTED_TEXT_COLOR + else NORMAL_TEXT_COLOR + ) } + } - private fun setStatus(status: Boolean?) { - val button = toggleButton - if (button != null && status != null) { - button.isChecked = status - button.setTextColor( - if (status) SELECTED_TEXT_COLOR - else NORMAL_TEXT_COLOR - ) - } - } - - fun readState(): Boolean { - val button = toggleButton ?: return false - - if (button.isPressed) return true - val result = button.isChecked - if (result) { - button.isChecked = false - button.setTextColor(NORMAL_TEXT_COLOR) - } - return result + fun readState(): Boolean { + val button = toggleButton ?: return false + + if (button.isPressed) return true + val result = button.isChecked + if (result) { + button.isChecked = false + button.setTextColor(NORMAL_TEXT_COLOR) } + return result + } } diff --git a/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/TextButton.kt b/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/TextButton.kt index d9e0df5..9191a7e 100644 --- a/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/TextButton.kt +++ b/app/src/main/java/io/neoterm/frontend/terminal/extrakey/button/TextButton.kt @@ -11,21 +11,21 @@ import io.neoterm.frontend.terminal.extrakey.combine.CombinedSequence */ open class TextButton constructor(text: String, val withEnter: Boolean = false) : IExtraButton() { - init { - this.buttonKeys = CombinedSequence.solveString(text) - this.displayText = text - } + init { + this.buttonKeys = CombinedSequence.solveString(text) + this.displayText = text + } - override fun onClick(view: View) { - buttonKeys!!.keys.forEach { - sendKey(view, it) - } - if (withEnter) { - sendKey(view, "\n") - } + override fun onClick(view: View) { + buttonKeys!!.keys.forEach { + sendKey(view, it) } + if (withEnter) { + sendKey(view, "\n") + } + } - override fun makeButton(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): Button { - return Button(context, attrs, defStyleAttr) - } + override fun makeButton(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): Button { + return Button(context, attrs, defStyleAttr) + } } diff --git a/app/src/main/java/io/neoterm/frontend/terminal/extrakey/combine/CombinedSequence.kt b/app/src/main/java/io/neoterm/frontend/terminal/extrakey/combine/CombinedSequence.kt index bca4116..d3227fc 100644 --- a/app/src/main/java/io/neoterm/frontend/terminal/extrakey/combine/CombinedSequence.kt +++ b/app/src/main/java/io/neoterm/frontend/terminal/extrakey/combine/CombinedSequence.kt @@ -8,22 +8,22 @@ package io.neoterm.frontend.terminal.extrakey.combine * @author kiva */ class CombinedSequence private constructor() { - val keys = mutableListOf() + val keys = mutableListOf() - companion object { - fun solveString(keyString: String): CombinedSequence { - val seq = CombinedSequence() - keyString.split(' ').forEach { - val key = if (it.startsWith('<') && it.endsWith('>')) { - // is a sequence - it.substring(1, it.length - 1) - } else { - // is a normal string - it - } - seq.keys.add(key) - } - return seq + companion object { + fun solveString(keyString: String): CombinedSequence { + val seq = CombinedSequence() + keyString.split(' ').forEach { + val key = if (it.startsWith('<') && it.endsWith('>')) { + // is a sequence + it.substring(1, it.length - 1) + } else { + // is a normal string + it } + seq.keys.add(key) + } + return seq } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/services/NeoTermService.kt b/app/src/main/java/io/neoterm/services/NeoTermService.kt index 3c7f8a2..c9d0472 100644 --- a/app/src/main/java/io/neoterm/services/NeoTermService.kt +++ b/app/src/main/java/io/neoterm/services/NeoTermService.kt @@ -9,8 +9,8 @@ import android.os.Binder import android.os.Build import android.os.IBinder import android.os.PowerManager -import androidx.core.app.NotificationCompat import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.NotificationCompat import io.neoterm.R import io.neoterm.backend.EmulatorDebug import io.neoterm.backend.TerminalSession @@ -27,193 +27,200 @@ import io.neoterm.utils.TerminalUtils */ class NeoTermService : Service() { - inner class NeoTermBinder : Binder() { - var service = this@NeoTermService - } + inner class NeoTermBinder : Binder() { + var service = this@NeoTermService + } - private val serviceBinder = NeoTermBinder() - private val mTerminalSessions = ArrayList() - private val mXSessions = ArrayList() - private var mWakeLock: PowerManager.WakeLock? = null - private var mWifiLock: WifiManager.WifiLock? = null + private val serviceBinder = NeoTermBinder() + private val mTerminalSessions = ArrayList() + private val mXSessions = ArrayList() + private var mWakeLock: PowerManager.WakeLock? = null + private var mWifiLock: WifiManager.WifiLock? = null - override fun onCreate() { - super.onCreate() - createNotificationChannel() - startForeground(NOTIFICATION_ID, createNotification()) - } + override fun onCreate() { + super.onCreate() + createNotificationChannel() + startForeground(NOTIFICATION_ID, createNotification()) + } - override fun onBind(intent: Intent): IBinder? { - return serviceBinder - } - - override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { - val action = intent.action - when (action) { - ACTION_SERVICE_STOP -> { - for (i in mTerminalSessions.indices) - mTerminalSessions[i].finishIfRunning() - stopSelf() - } - - ACTION_ACQUIRE_LOCK -> acquireLock() - - ACTION_RELEASE_LOCK -> releaseLock() - } - - return Service.START_NOT_STICKY - } - - override fun onDestroy() { - stopForeground(true) + override fun onBind(intent: Intent): IBinder? { + return serviceBinder + } + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + val action = intent.action + when (action) { + ACTION_SERVICE_STOP -> { for (i in mTerminalSessions.indices) - mTerminalSessions[i].finishIfRunning() - mTerminalSessions.clear() + mTerminalSessions[i].finishIfRunning() + stopSelf() + } + + ACTION_ACQUIRE_LOCK -> acquireLock() + + ACTION_RELEASE_LOCK -> releaseLock() } - val sessions: List - get() = mTerminalSessions + return Service.START_NOT_STICKY + } - val xSessions: List - get() = mXSessions + override fun onDestroy() { + stopForeground(true) - fun createTermSession(parameter: ShellParameter): TerminalSession { - val session = createOrFindSession(parameter) - updateNotification() - return session + for (i in mTerminalSessions.indices) + mTerminalSessions[i].finishIfRunning() + mTerminalSessions.clear() + } + + val sessions: List + get() = mTerminalSessions + + val xSessions: List + get() = mXSessions + + fun createTermSession(parameter: ShellParameter): TerminalSession { + val session = createOrFindSession(parameter) + updateNotification() + return session + } + + fun removeTermSession(sessionToRemove: TerminalSession): Int { + val indexOfRemoved = mTerminalSessions.indexOf(sessionToRemove) + if (indexOfRemoved >= 0) { + mTerminalSessions.removeAt(indexOfRemoved) + updateNotification() + } + return indexOfRemoved + } + + fun createXSession(activity: AppCompatActivity, parameter: XParameter): XSession { + val session = TerminalUtils.createSession(activity, parameter) + mXSessions.add(session) + updateNotification() + return session + } + + fun removeXSession(sessionToRemove: XSession): Int { + val indexOfRemoved = mXSessions.indexOf(sessionToRemove) + if (indexOfRemoved >= 0) { + mXSessions.removeAt(indexOfRemoved) + updateNotification() + } + return indexOfRemoved + } + + private fun createOrFindSession(parameter: ShellParameter): TerminalSession { + if (parameter.willCreateNewSession()) { + NLog.d("createOrFindSession: creating new session") + val session = TerminalUtils.createSession(this, parameter) + mTerminalSessions.add(session) + return session } - fun removeTermSession(sessionToRemove: TerminalSession): Int { - val indexOfRemoved = mTerminalSessions.indexOf(sessionToRemove) - if (indexOfRemoved >= 0) { - mTerminalSessions.removeAt(indexOfRemoved) - updateNotification() - } - return indexOfRemoved + val sessionId = parameter.sessionId!! + NLog.d("createOrFindSession: find session by id $sessionId") + + val session = mTerminalSessions.find { it.mHandle == sessionId.sessionId } + ?: throw IllegalArgumentException("cannot find session by given id") + + session.write(parameter.initialCommand + "\n") + return session + } + + private fun updateNotification() { + val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + service.notify(NOTIFICATION_ID, createNotification()) + } + + private fun createNotification(): Notification { + val notifyIntent = Intent(this, NeoTermActivity::class.java) + notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + val pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0) + + val sessionCount = mTerminalSessions.size + val xSessionCount = mXSessions.size + var contentText = getString(R.string.service_status_text, sessionCount, xSessionCount) + + val lockAcquired = mWakeLock != null + if (lockAcquired) contentText += getString(R.string.service_lock_acquired) + + val builder = NotificationCompat.Builder(this, DEFAULT_CHANNEL_ID) + builder.setContentTitle(getText(R.string.app_name)) + builder.setContentText(contentText) + builder.setSmallIcon(R.drawable.ic_terminal_running) + builder.setContentIntent(pendingIntent) + builder.setOngoing(true) + builder.setShowWhen(false) + builder.color = 0xFF000000.toInt() + + builder.priority = if (lockAcquired) Notification.PRIORITY_HIGH else Notification.PRIORITY_LOW + + val exitIntent = Intent(this, NeoTermService::class.java).setAction(ACTION_SERVICE_STOP) + builder.addAction( + android.R.drawable.ic_delete, + getString(R.string.exit), + PendingIntent.getService(this, 0, exitIntent, 0) + ) + + val newWakeAction = if (lockAcquired) ACTION_RELEASE_LOCK else ACTION_ACQUIRE_LOCK + val toggleWakeLockIntent = Intent(this, NeoTermService::class.java).setAction(newWakeAction) + val actionTitle = getString( + if (lockAcquired) + R.string.service_release_lock + else + R.string.service_acquire_lock + ) + val actionIcon = if (lockAcquired) android.R.drawable.ic_lock_idle_lock else android.R.drawable.ic_lock_lock + builder.addAction(actionIcon, actionTitle, PendingIntent.getService(this, 0, toggleWakeLockIntent, 0)) + + return builder.build() + } + + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return + + val channel = NotificationChannel(DEFAULT_CHANNEL_ID, "NeoTerm", NotificationManager.IMPORTANCE_LOW) + channel.description = "NeoTerm notifications" + val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + manager.createNotificationChannel(channel) + } + + @SuppressLint("WakelockTimeout") + private fun acquireLock() { + if (mWakeLock == null) { + val pm = getSystemService(Context.POWER_SERVICE) as PowerManager + mWakeLock = pm.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, + EmulatorDebug.LOG_TAG + ":" + ) + mWakeLock!!.acquire() + + val wm = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG) + mWifiLock!!.acquire() + + updateNotification() } + } - fun createXSession(activity: AppCompatActivity, parameter: XParameter): XSession { - val session = TerminalUtils.createSession(activity, parameter) - mXSessions.add(session) - updateNotification() - return session + private fun releaseLock() { + if (mWakeLock != null) { + mWakeLock!!.release() + mWakeLock = null + + mWifiLock!!.release() + mWifiLock = null + + updateNotification() } + } - fun removeXSession(sessionToRemove: XSession): Int { - val indexOfRemoved = mXSessions.indexOf(sessionToRemove) - if (indexOfRemoved >= 0) { - mXSessions.removeAt(indexOfRemoved) - updateNotification() - } - return indexOfRemoved - } + companion object { + val ACTION_SERVICE_STOP = "neoterm.action.service.stop" + val ACTION_ACQUIRE_LOCK = "neoterm.action.service.lock.acquire" + val ACTION_RELEASE_LOCK = "neoterm.action.service.lock.release" + private val NOTIFICATION_ID = 52019 - private fun createOrFindSession(parameter: ShellParameter): TerminalSession { - if (parameter.willCreateNewSession()) { - NLog.d("createOrFindSession: creating new session") - val session = TerminalUtils.createSession(this, parameter) - mTerminalSessions.add(session) - return session - } - - val sessionId = parameter.sessionId!! - NLog.d("createOrFindSession: find session by id $sessionId") - - val session = mTerminalSessions.find { it.mHandle == sessionId.sessionId } - ?: throw IllegalArgumentException("cannot find session by given id") - - session.write(parameter.initialCommand + "\n") - return session - } - - private fun updateNotification() { - val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - service.notify(NOTIFICATION_ID, createNotification()) - } - - private fun createNotification(): Notification { - val notifyIntent = Intent(this, NeoTermActivity::class.java) - notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - val pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0) - - val sessionCount = mTerminalSessions.size - val xSessionCount = mXSessions.size - var contentText = getString(R.string.service_status_text, sessionCount, xSessionCount) - - val lockAcquired = mWakeLock != null - if (lockAcquired) contentText += getString(R.string.service_lock_acquired) - - val builder = NotificationCompat.Builder(this, DEFAULT_CHANNEL_ID) - builder.setContentTitle(getText(R.string.app_name)) - builder.setContentText(contentText) - builder.setSmallIcon(R.drawable.ic_terminal_running) - builder.setContentIntent(pendingIntent) - builder.setOngoing(true) - builder.setShowWhen(false) - builder.color = 0xFF000000.toInt() - - builder.priority = if (lockAcquired) Notification.PRIORITY_HIGH else Notification.PRIORITY_LOW - - val exitIntent = Intent(this, NeoTermService::class.java).setAction(ACTION_SERVICE_STOP) - builder.addAction(android.R.drawable.ic_delete, getString(R.string.exit), PendingIntent.getService(this, 0, exitIntent, 0)) - - val newWakeAction = if (lockAcquired) ACTION_RELEASE_LOCK else ACTION_ACQUIRE_LOCK - val toggleWakeLockIntent = Intent(this, NeoTermService::class.java).setAction(newWakeAction) - val actionTitle = getString( - if (lockAcquired) - R.string.service_release_lock - else - R.string.service_acquire_lock) - val actionIcon = if (lockAcquired) android.R.drawable.ic_lock_idle_lock else android.R.drawable.ic_lock_lock - builder.addAction(actionIcon, actionTitle, PendingIntent.getService(this, 0, toggleWakeLockIntent, 0)) - - return builder.build() - } - - private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return - - val channel = NotificationChannel(DEFAULT_CHANNEL_ID, "NeoTerm", NotificationManager.IMPORTANCE_LOW) - channel.description = "NeoTerm notifications" - val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - manager.createNotificationChannel(channel) - } - - @SuppressLint("WakelockTimeout") - private fun acquireLock() { - if (mWakeLock == null) { - val pm = getSystemService(Context.POWER_SERVICE) as PowerManager - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - EmulatorDebug.LOG_TAG + ":") - mWakeLock!!.acquire() - - val wm = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager - mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG) - mWifiLock!!.acquire() - - updateNotification() - } - } - - private fun releaseLock() { - if (mWakeLock != null) { - mWakeLock!!.release() - mWakeLock = null - - mWifiLock!!.release() - mWifiLock = null - - updateNotification() - } - } - - companion object { - val ACTION_SERVICE_STOP = "neoterm.action.service.stop" - val ACTION_ACQUIRE_LOCK = "neoterm.action.service.lock.acquire" - val ACTION_RELEASE_LOCK = "neoterm.action.service.lock.release" - private val NOTIFICATION_ID = 52019 - - val DEFAULT_CHANNEL_ID = "neoterm_notification_channel" - } + val DEFAULT_CHANNEL_ID = "neoterm_notification_channel" + } } diff --git a/app/src/main/java/io/neoterm/setup/ResultListener.kt b/app/src/main/java/io/neoterm/setup/ResultListener.kt index c1eac6a..3546064 100644 --- a/app/src/main/java/io/neoterm/setup/ResultListener.kt +++ b/app/src/main/java/io/neoterm/setup/ResultListener.kt @@ -4,5 +4,5 @@ package io.neoterm.setup * @author kiva */ interface ResultListener { - fun onResult(error: Exception?) + fun onResult(error: Exception?) } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/setup/SetupHelper.kt b/app/src/main/java/io/neoterm/setup/SetupHelper.kt index 713eadb..81658a5 100644 --- a/app/src/main/java/io/neoterm/setup/SetupHelper.kt +++ b/app/src/main/java/io/neoterm/setup/SetupHelper.kt @@ -1,10 +1,10 @@ package io.neoterm.setup -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.app.AlertDialog import android.app.ProgressDialog import android.content.Context import android.os.Build +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity import io.neoterm.App import io.neoterm.R import io.neoterm.frontend.config.NeoTermPath @@ -15,63 +15,67 @@ import java.util.* * @author kiva */ object SetupHelper { - fun needSetup(): Boolean { - val PREFIX_FILE = File(NeoTermPath.USR_PATH) - return !PREFIX_FILE.isDirectory + fun needSetup(): Boolean { + val PREFIX_FILE = File(NeoTermPath.USR_PATH) + return !PREFIX_FILE.isDirectory + } + + fun setup( + activity: AppCompatActivity, connection: SourceConnection, + resultListener: ResultListener + ) { + if (!needSetup()) { + resultListener.onResult(null) + return } - fun setup(activity: AppCompatActivity, connection: SourceConnection, - resultListener: ResultListener) { - if (!needSetup()) { - resultListener.onResult(null) - return - } + val prefixFile = File(NeoTermPath.USR_PATH) - val prefixFile = File(NeoTermPath.USR_PATH) + val progress = makeProgressDialog(activity) + progress.max = 100 + progress.show() - val progress = makeProgressDialog(activity) - progress.max = 100 - progress.show() + SetupThread(activity, connection, prefixFile, resultListener, progress) + .start() + } - SetupThread(activity, connection, prefixFile, resultListener, progress) - .start() - } - - private fun makeProgressDialog(context: Context): ProgressDialog { - return makeProgressDialog(context, context.getString(R.string.installer_message)) - } - - fun makeProgressDialog(context: Context, message: String): ProgressDialog { - val dialog = ProgressDialog(context) - dialog.setMessage(message) - dialog.isIndeterminate = false - dialog.setCancelable(false) - dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) - return dialog - } - - fun makeErrorDialog(context: Context, messageId: Int): AlertDialog { - return makeErrorDialog(context, context.getString(messageId)) - } - - fun makeErrorDialog(context: Context, message: String): AlertDialog { - return AlertDialog.Builder(context) - .setTitle(R.string.error) - .setMessage(message) - .setPositiveButton(android.R.string.yes, null) - .setNeutralButton(R.string.show_help) { _, _ -> App.get().openHelpLink() } - .create() - } - - fun determineArchName(): String { - for (androidArch in Build.SUPPORTED_ABIS) { - when (androidArch) { - "arm64-v8a" -> return "aarch64" - "armeabi-v7a" -> return "arm" - "x86_64" -> return "x86_64" - } - } - throw RuntimeException("Unable to determine arch from Build.SUPPORTED_ABIS = " - + Arrays.toString(Build.SUPPORTED_ABIS)) + private fun makeProgressDialog(context: Context): ProgressDialog { + return makeProgressDialog(context, context.getString(R.string.installer_message)) + } + + fun makeProgressDialog(context: Context, message: String): ProgressDialog { + val dialog = ProgressDialog(context) + dialog.setMessage(message) + dialog.isIndeterminate = false + dialog.setCancelable(false) + dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + return dialog + } + + fun makeErrorDialog(context: Context, messageId: Int): AlertDialog { + return makeErrorDialog(context, context.getString(messageId)) + } + + fun makeErrorDialog(context: Context, message: String): AlertDialog { + return AlertDialog.Builder(context) + .setTitle(R.string.error) + .setMessage(message) + .setPositiveButton(android.R.string.yes, null) + .setNeutralButton(R.string.show_help) { _, _ -> App.get().openHelpLink() } + .create() + } + + fun determineArchName(): String { + for (androidArch in Build.SUPPORTED_ABIS) { + when (androidArch) { + "arm64-v8a" -> return "aarch64" + "armeabi-v7a" -> return "arm" + "x86_64" -> return "x86_64" + } } + throw RuntimeException( + "Unable to determine arch from Build.SUPPORTED_ABIS = " + + Arrays.toString(Build.SUPPORTED_ABIS) + ) + } } diff --git a/app/src/main/java/io/neoterm/setup/SetupThread.java b/app/src/main/java/io/neoterm/setup/SetupThread.java index 5d3dac1..00bf47b 100644 --- a/app/src/main/java/io/neoterm/setup/SetupThread.java +++ b/app/src/main/java/io/neoterm/setup/SetupThread.java @@ -1,165 +1,160 @@ package io.neoterm.setup; import android.app.ProgressDialog; -import androidx.appcompat.app.AppCompatActivity; import android.system.Os; import android.util.Pair; +import androidx.appcompat.app.AppCompatActivity; +import io.neoterm.backend.EmulatorDebug; +import io.neoterm.frontend.config.NeoTermPath; +import io.neoterm.frontend.logging.NLog; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; +import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import io.neoterm.backend.EmulatorDebug; -import io.neoterm.frontend.config.NeoTermPath; -import io.neoterm.frontend.logging.NLog; - /** * @author kiva */ final class SetupThread extends Thread { - private final SourceConnection sourceConnection; - private final File prefixPath; - private final AppCompatActivity activity; - private final ResultListener resultListener; - private final ProgressDialog progressDialog; + private final SourceConnection sourceConnection; + private final File prefixPath; + private final AppCompatActivity activity; + private final ResultListener resultListener; + private final ProgressDialog progressDialog; - public SetupThread(AppCompatActivity activity, SourceConnection sourceConnection, - File prefixPath, ResultListener resultListener, - ProgressDialog progressDialog) { - this.activity = activity; - this.sourceConnection = sourceConnection; - this.prefixPath = prefixPath; - this.resultListener = resultListener; - this.progressDialog = progressDialog; - } + public SetupThread(AppCompatActivity activity, SourceConnection sourceConnection, + File prefixPath, ResultListener resultListener, + ProgressDialog progressDialog) { + this.activity = activity; + this.sourceConnection = sourceConnection; + this.prefixPath = prefixPath; + this.resultListener = resultListener; + this.progressDialog = progressDialog; + } - @Override - public void run() { + @Override + public void run() { + try { + final String stagingPrefixPath = NeoTermPath.ROOT_PATH + "/usr-staging"; + final File stagingPrefixFile = new File(stagingPrefixPath); + + if (stagingPrefixFile.exists()) { + deleteFolder(stagingPrefixFile); + } + + int totalReadBytes = 0; + final byte[] buffer = new byte[8096]; + final List> symlinks = new ArrayList<>(50); + + + try (ZipInputStream zipInput = new ZipInputStream(sourceConnection.getInputStream())) { + ZipEntry zipEntry; + + int totalBytes = sourceConnection.getSize(); + + while ((zipEntry = zipInput.getNextEntry()) != null) { + totalReadBytes += zipEntry.getCompressedSize(); + + final int totalReadBytesFinal = totalReadBytes; + final int totalBytesFinal = totalBytes; + + activity.runOnUiThread(() -> { + try { + double progressFloat = ((double) totalReadBytesFinal) / ((double) totalBytesFinal) * 100.0; + progressDialog.setProgress((int) progressFloat); + } catch (RuntimeException ignore) { + // activity dismissed + } + }); + + if (zipEntry.getName().contains("SYMLINKS.txt")) { + BufferedReader symlinksReader = new BufferedReader(new InputStreamReader(zipInput)); + String line; + while ((line = symlinksReader.readLine()) != null) { + if (line.isEmpty()) { + continue; + } + String[] parts = line.split("←"); + if (parts.length != 2) + throw new RuntimeException("Malformed symlink line: " + line); + String oldPath = parts[0]; + String newPath = stagingPrefixPath + "/" + parts[1]; + symlinks.add(Pair.create(oldPath, newPath)); + } + } else { + String zipEntryName = zipEntry.getName(); + File targetFile = new File(stagingPrefixPath, zipEntryName); + if (zipEntry.isDirectory()) { + if (!targetFile.mkdirs()) + throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath()); + } else { + try (FileOutputStream outStream = new FileOutputStream(targetFile)) { + int readBytes; + while ((readBytes = zipInput.read(buffer)) != -1) { + outStream.write(buffer, 0, readBytes); + } + } + if (zipEntryName.startsWith("bin/") || zipEntryName.startsWith("libexec") || zipEntryName.startsWith("lib/apt/methods")) { + //noinspection OctalInteger + Os.chmod(targetFile.getAbsolutePath(), 0700); + } + } + } + } + } + + sourceConnection.close(); + + if (symlinks.isEmpty()) + throw new RuntimeException("No SYMLINKS.txt encountered"); + for (Pair symlink : symlinks) { + NLog.INSTANCE.e("Setup", "Linking " + symlink.first + " to " + symlink.second); + Os.symlink(symlink.first, symlink.second); + } + + if (!stagingPrefixFile.renameTo(prefixPath)) { + throw new RuntimeException("Unable to rename staging folder"); + } + + activity.runOnUiThread(() -> resultListener.onResult(null)); + } catch (final Exception e) { + NLog.INSTANCE.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e); + activity.runOnUiThread(() -> { try { - final String stagingPrefixPath = NeoTermPath.ROOT_PATH + "/usr-staging"; - final File stagingPrefixFile = new File(stagingPrefixPath); - - if (stagingPrefixFile.exists()) { - deleteFolder(stagingPrefixFile); - } - - int totalReadBytes = 0; - final byte[] buffer = new byte[8096]; - final List> symlinks = new ArrayList<>(50); - - - try (ZipInputStream zipInput = new ZipInputStream(sourceConnection.getInputStream())) { - ZipEntry zipEntry; - - int totalBytes = sourceConnection.getSize(); - - while ((zipEntry = zipInput.getNextEntry()) != null) { - totalReadBytes += zipEntry.getCompressedSize(); - - final int totalReadBytesFinal = totalReadBytes; - final int totalBytesFinal = totalBytes; - - activity.runOnUiThread(() -> { - try { - double progressFloat = ((double) totalReadBytesFinal) / ((double) totalBytesFinal) * 100.0; - progressDialog.setProgress((int) progressFloat); - } catch (RuntimeException ignore) { - // activity dismissed - } - }); - - if (zipEntry.getName().contains("SYMLINKS.txt")) { - BufferedReader symlinksReader = new BufferedReader(new InputStreamReader(zipInput)); - String line; - while ((line = symlinksReader.readLine()) != null) { - if (line.isEmpty()) { - continue; - } - String[] parts = line.split("←"); - if (parts.length != 2) - throw new RuntimeException("Malformed symlink line: " + line); - String oldPath = parts[0]; - String newPath = stagingPrefixPath + "/" + parts[1]; - symlinks.add(Pair.create(oldPath, newPath)); - } - } else { - String zipEntryName = zipEntry.getName(); - File targetFile = new File(stagingPrefixPath, zipEntryName); - if (zipEntry.isDirectory()) { - if (!targetFile.mkdirs()) - throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath()); - } else { - try (FileOutputStream outStream = new FileOutputStream(targetFile)) { - int readBytes; - while ((readBytes = zipInput.read(buffer)) != -1) { - outStream.write(buffer, 0, readBytes); - } - } - if (zipEntryName.startsWith("bin/") || zipEntryName.startsWith("libexec") || zipEntryName.startsWith("lib/apt/methods")) { - //noinspection OctalInteger - Os.chmod(targetFile.getAbsolutePath(), 0700); - } - } - } - } - } - - sourceConnection.close(); - - if (symlinks.isEmpty()) - throw new RuntimeException("No SYMLINKS.txt encountered"); - for (Pair symlink : symlinks) { - NLog.INSTANCE.e("Setup", "Linking " + symlink.first + " to " + symlink.second); - Os.symlink(symlink.first, symlink.second); - } - - if (!stagingPrefixFile.renameTo(prefixPath)) { - throw new RuntimeException("Unable to rename staging folder"); - } - - activity.runOnUiThread(() -> resultListener.onResult(null)); - } catch (final Exception e) { - NLog.INSTANCE.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e); - activity.runOnUiThread(() -> { - try { - resultListener.onResult(e); - } catch (RuntimeException e1) { - // Activity already dismissed - ignore. - } - }); - } finally { - activity.runOnUiThread(() -> { - try { - progressDialog.dismiss(); - } catch (RuntimeException e) { - // Activity already dismissed - ignore. - } - }); + resultListener.onResult(e); + } catch (RuntimeException e1) { + // Activity already dismissed - ignore. } + }); + } finally { + activity.runOnUiThread(() -> { + try { + progressDialog.dismiss(); + } catch (RuntimeException e) { + // Activity already dismissed - ignore. + } + }); + } + } + + private static void deleteFolder(File fileOrDirectory) throws IOException { + if (fileOrDirectory.getCanonicalPath().equals(fileOrDirectory.getAbsolutePath()) && fileOrDirectory.isDirectory()) { + File[] children = fileOrDirectory.listFiles(); + + if (children != null) { + for (File child : children) { + deleteFolder(child); + } + } } - private static void deleteFolder(File fileOrDirectory) throws IOException { - if (fileOrDirectory.getCanonicalPath().equals(fileOrDirectory.getAbsolutePath()) && fileOrDirectory.isDirectory()) { - File[] children = fileOrDirectory.listFiles(); - - if (children != null) { - for (File child : children) { - deleteFolder(child); - } - } - } - - if (!fileOrDirectory.delete()) { - throw new RuntimeException("Unable to delete " - + (fileOrDirectory.isDirectory() ? "directory " : "file ") - + fileOrDirectory.getAbsolutePath()); - } + if (!fileOrDirectory.delete()) { + throw new RuntimeException("Unable to delete " + + (fileOrDirectory.isDirectory() ? "directory " : "file ") + + fileOrDirectory.getAbsolutePath()); } + } } diff --git a/app/src/main/java/io/neoterm/setup/SourceConnection.java b/app/src/main/java/io/neoterm/setup/SourceConnection.java index fd8b0a6..4c73bea 100644 --- a/app/src/main/java/io/neoterm/setup/SourceConnection.java +++ b/app/src/main/java/io/neoterm/setup/SourceConnection.java @@ -7,9 +7,9 @@ import java.io.InputStream; * @author kiva */ public interface SourceConnection { - InputStream getInputStream() throws IOException; + InputStream getInputStream() throws IOException; - int getSize(); + int getSize(); - void close(); + void close(); } diff --git a/app/src/main/java/io/neoterm/setup/connections/NetworkConnection.kt b/app/src/main/java/io/neoterm/setup/connections/NetworkConnection.kt index d5d8f4e..3b11fc8 100644 --- a/app/src/main/java/io/neoterm/setup/connections/NetworkConnection.kt +++ b/app/src/main/java/io/neoterm/setup/connections/NetworkConnection.kt @@ -12,35 +12,35 @@ import java.net.URL */ class NetworkConnection(private val sourceUrl: String) : SourceConnection { - private var connection: HttpURLConnection? = null + private var connection: HttpURLConnection? = null - @Throws(IOException::class) - override fun getInputStream(): InputStream { - if (connection == null) { - connection = openHttpConnection() - connection!!.connectTimeout = 8000 - connection!!.readTimeout = 8000 - } - return connection!!.inputStream + @Throws(IOException::class) + override fun getInputStream(): InputStream { + if (connection == null) { + connection = openHttpConnection() + connection!!.connectTimeout = 8000 + connection!!.readTimeout = 8000 } + return connection!!.inputStream + } - override fun getSize(): Int { - return if (connection != null) { - connection!!.contentLength - } else 0 + override fun getSize(): Int { + return if (connection != null) { + connection!!.contentLength + } else 0 + } + + override fun close() { + if (connection != null) { + connection!!.disconnect() } + } - override fun close() { - if (connection != null) { - connection!!.disconnect() - } - } + @Throws(IOException::class) + private fun openHttpConnection(): HttpURLConnection { + val arch = SetupHelper.determineArchName() - @Throws(IOException::class) - private fun openHttpConnection(): HttpURLConnection { - val arch = SetupHelper.determineArchName() - - return URL("$sourceUrl/boot/$arch.zip").openConnection() as HttpURLConnection - } + return URL("$sourceUrl/boot/$arch.zip").openConnection() as HttpURLConnection + } } diff --git a/app/src/main/java/io/neoterm/setup/connections/OfflineConnection.kt b/app/src/main/java/io/neoterm/setup/connections/OfflineConnection.kt index 70db119..a1b8060 100644 --- a/app/src/main/java/io/neoterm/setup/connections/OfflineConnection.kt +++ b/app/src/main/java/io/neoterm/setup/connections/OfflineConnection.kt @@ -9,40 +9,40 @@ import java.io.InputStream */ abstract class OfflineConnection : SourceConnection { - private var inputStream: InputStream? = null + private var inputStream: InputStream? = null - @Throws(IOException::class) - protected abstract fun openInputStream(): InputStream + @Throws(IOException::class) + protected abstract fun openInputStream(): InputStream - @Throws(IOException::class) - override fun getInputStream(): InputStream { - if (inputStream == null) { - inputStream = openInputStream() - } - return inputStream!! + @Throws(IOException::class) + override fun getInputStream(): InputStream { + if (inputStream == null) { + inputStream = openInputStream() } + return inputStream!! + } - override fun getSize(): Int { - if (inputStream != null) { - return try { - inputStream!!.available() - } catch (e: IOException) { - e.printStackTrace() - 0 - } + override fun getSize(): Int { + if (inputStream != null) { + return try { + inputStream!!.available() + } catch (e: IOException) { + e.printStackTrace() + 0 + } - } - return 0 } + return 0 + } - override fun close() { - if (inputStream != null) { - try { - inputStream!!.close() - } catch (ignore: IOException) { - ignore.printStackTrace() - } + override fun close() { + if (inputStream != null) { + try { + inputStream!!.close() + } catch (ignore: IOException) { + ignore.printStackTrace() + } - } } + } } diff --git a/app/src/main/java/io/neoterm/setup/connections/OfflineUriConnection.kt b/app/src/main/java/io/neoterm/setup/connections/OfflineUriConnection.kt index 72052cb..5fa6193 100644 --- a/app/src/main/java/io/neoterm/setup/connections/OfflineUriConnection.kt +++ b/app/src/main/java/io/neoterm/setup/connections/OfflineUriConnection.kt @@ -12,8 +12,8 @@ import java.io.InputStream open class OfflineUriConnection(private val context: Context, private val uri: Uri) : OfflineConnection() { - @Throws(IOException::class) - override fun openInputStream(): InputStream { - return context.contentResolver.openInputStream(uri) - } + @Throws(IOException::class) + override fun openInputStream(): InputStream { + return context.contentResolver.openInputStream(uri) + } } diff --git a/app/src/main/java/io/neoterm/ui/bonus/BonusActivity.kt b/app/src/main/java/io/neoterm/ui/bonus/BonusActivity.kt index 2c806e5..517f841 100644 --- a/app/src/main/java/io/neoterm/ui/bonus/BonusActivity.kt +++ b/app/src/main/java/io/neoterm/ui/bonus/BonusActivity.kt @@ -12,11 +12,11 @@ import android.graphics.drawable.RippleDrawable import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.shapes.OvalShape import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import android.view.* import android.view.animation.PathInterpolator import android.widget.FrameLayout import android.widget.ImageView +import androidx.appcompat.app.AppCompatActivity import io.neoterm.R /** @@ -25,150 +25,161 @@ import io.neoterm.R class BonusActivity : AppCompatActivity() { - lateinit internal var mLayout: FrameLayout - internal var mTapCount: Int = 0 - internal var mKeyCount: Int = 0 - internal var mInterpolator = PathInterpolator(0f, 0f, 0.5f, 1f) + lateinit internal var mLayout: FrameLayout + internal var mTapCount: Int = 0 + internal var mKeyCount: Int = 0 + internal var mInterpolator = PathInterpolator(0f, 0f, 0.5f, 1f) - internal fun makeRipple(): Drawable { - val idx = newColorIndex() - val lollipopBackground = ShapeDrawable(OvalShape()) - lollipopBackground.paint.color = FLAVORS[idx] - return RippleDrawable( - ColorStateList.valueOf(FLAVORS[idx + 1]), - lollipopBackground, null) - } + internal fun makeRipple(): Drawable { + val idx = newColorIndex() + val lollipopBackground = ShapeDrawable(OvalShape()) + lollipopBackground.paint.color = FLAVORS[idx] + return RippleDrawable( + ColorStateList.valueOf(FLAVORS[idx + 1]), + lollipopBackground, null + ) + } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - mLayout = FrameLayout(this) - window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN) - setContentView(mLayout) - } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mLayout = FrameLayout(this) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(mLayout) + } - override fun onAttachedToWindow() { - val dm = resources.displayMetrics - val dp = dm.density - val size = (Math.min(Math.min(dm.widthPixels, dm.heightPixels).toFloat(), 600 * dp) - 100 * dp).toInt() - val stick = object : View(this) { - internal var mPaint = Paint() - internal var mShadow = Path() + override fun onAttachedToWindow() { + val dm = resources.displayMetrics + val dp = dm.density + val size = (Math.min(Math.min(dm.widthPixels, dm.heightPixels).toFloat(), 600 * dp) - 100 * dp).toInt() + val stick = object : View(this) { + internal var mPaint = Paint() + internal var mShadow = Path() - public override fun onAttachedToWindow() { - super.onAttachedToWindow() - setWillNotDraw(false) - outlineProvider = object : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - outline.setRect(0, height / 2, width, height) - } - } - } - - public override fun onDraw(c: Canvas) { - val w = c.width - val h = c.height / 2 - c.translate(0f, h.toFloat()) - val g = GradientDrawable() - g.orientation = GradientDrawable.Orientation.LEFT_RIGHT - g.setGradientCenter(w * 0.75f, 0f) - g.colors = intArrayOf(0xFFFFFFFF.toInt(), 0xFFAAAAAA.toInt()) - g.setBounds(0, 0, w, h) - g.draw(c) - mPaint.color = 0xFFAAAAAA.toInt() - mShadow.reset() - mShadow.moveTo(0f, 0f) - mShadow.lineTo(w.toFloat(), 0f) - mShadow.lineTo(w.toFloat(), size / 2 + 1.5f * w) - mShadow.lineTo(0f, (size / 2).toFloat()) - mShadow.close() - c.drawPath(mShadow, mPaint) - } + public override fun onAttachedToWindow() { + super.onAttachedToWindow() + setWillNotDraw(false) + outlineProvider = object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRect(0, height / 2, width, height) + } } - mLayout.addView(stick, FrameLayout.LayoutParams((32 * dp).toInt(), - ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER_HORIZONTAL)) - stick.alpha = 0f + } - val im = ImageView(this) - im.translationZ = 20f - im.scaleX = 0f - im.scaleY = 0f - val platlogo = getDrawable(R.drawable.plat_logo) - platlogo!!.alpha = 0 - im.setImageDrawable(platlogo) + public override fun onDraw(c: Canvas) { + val w = c.width + val h = c.height / 2 + c.translate(0f, h.toFloat()) + val g = GradientDrawable() + g.orientation = GradientDrawable.Orientation.LEFT_RIGHT + g.setGradientCenter(w * 0.75f, 0f) + g.colors = intArrayOf(0xFFFFFFFF.toInt(), 0xFFAAAAAA.toInt()) + g.setBounds(0, 0, w, h) + g.draw(c) + mPaint.color = 0xFFAAAAAA.toInt() + mShadow.reset() + mShadow.moveTo(0f, 0f) + mShadow.lineTo(w.toFloat(), 0f) + mShadow.lineTo(w.toFloat(), size / 2 + 1.5f * w) + mShadow.lineTo(0f, (size / 2).toFloat()) + mShadow.close() + c.drawPath(mShadow, mPaint) + } + } + mLayout.addView( + stick, FrameLayout.LayoutParams( + (32 * dp).toInt(), + ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER_HORIZONTAL + ) + ) + stick.alpha = 0f + + val im = ImageView(this) + im.translationZ = 20f + im.scaleX = 0f + im.scaleY = 0f + val platlogo = getDrawable(R.drawable.plat_logo) + platlogo!!.alpha = 0 + im.setImageDrawable(platlogo) + im.background = makeRipple() + im.isClickable = true + val highlight = ShapeDrawable(OvalShape()) + highlight.paint.color = 0x10FFFFFF + highlight.setBounds( + (size * .15f).toInt(), (size * .15f).toInt(), + (size * .6f).toInt(), (size * .6f).toInt() + ) + im.overlay.add(highlight) + im.setOnClickListener { + if (mTapCount == 0) { + im.animate() + .translationZ(40f) + .scaleX(1f) + .scaleY(1f) + .setInterpolator(mInterpolator) + .setDuration(700) + .setStartDelay(500) + .start() + + val a = ObjectAnimator.ofInt(platlogo, "alpha", 0, 255) + a.interpolator = mInterpolator + a.startDelay = 1000 + a.start() + + stick.animate() + .translationZ(20f) + .alpha(1f) + .setInterpolator(mInterpolator) + .setDuration(700) + .setStartDelay(750) + .start() + } else { im.background = makeRipple() - im.isClickable = true - val highlight = ShapeDrawable(OvalShape()) - highlight.paint.color = 0x10FFFFFF - highlight.setBounds((size * .15f).toInt(), (size * .15f).toInt(), - (size * .6f).toInt(), (size * .6f).toInt()) - im.overlay.add(highlight) - im.setOnClickListener { - if (mTapCount == 0) { - im.animate() - .translationZ(40f) - .scaleX(1f) - .scaleY(1f) - .setInterpolator(mInterpolator) - .setDuration(700) - .setStartDelay(500) - .start() - - val a = ObjectAnimator.ofInt(platlogo, "alpha", 0, 255) - a.interpolator = mInterpolator - a.startDelay = 1000 - a.start() - - stick.animate() - .translationZ(20f) - .alpha(1f) - .setInterpolator(mInterpolator) - .setDuration(700) - .setStartDelay(750) - .start() - } else { - im.background = makeRipple() - } - mTapCount++ - } - - // Enable hardware keyboard input for TV compatibility. - im.isFocusable = true - im.requestFocus() - im.setOnKeyListener { v, keyCode, event -> - if (keyCode != KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_DOWN) { - ++mKeyCount - if (mKeyCount > 2) { - if (mTapCount > 5) { - im.performLongClick() - } else { - im.performClick() - } - } - true - } else { - false - } - } - - mLayout.addView(im, FrameLayout.LayoutParams(size, size, Gravity.CENTER)) - - im.animate().scaleX(0.3f).scaleY(0.3f) - .setInterpolator(mInterpolator) - .setDuration(500) - .setStartDelay(800) - .start() + } + mTapCount++ } - companion object { - internal val FLAVORS = intArrayOf(0xFF9C27B0.toInt(), 0xFFBA68C8.toInt(), // grape - 0xFFFF9800.toInt(), 0xFFFFB74D.toInt(), // orange - 0xFFF06292.toInt(), 0xFFF8BBD0.toInt(), // bubblegum - 0xFFAFB42B.toInt(), 0xFFCDDC39.toInt(), // lime - 0xFF795548.toInt(), 0xFFA1887F.toInt())// mystery flavor - - internal fun newColorIndex(): Int { - return 2 * (Math.random() * FLAVORS.size / 2).toInt() + // Enable hardware keyboard input for TV compatibility. + im.isFocusable = true + im.requestFocus() + im.setOnKeyListener { v, keyCode, event -> + if (keyCode != KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_DOWN) { + ++mKeyCount + if (mKeyCount > 2) { + if (mTapCount > 5) { + im.performLongClick() + } else { + im.performClick() + } } + true + } else { + false + } } + + mLayout.addView(im, FrameLayout.LayoutParams(size, size, Gravity.CENTER)) + + im.animate().scaleX(0.3f).scaleY(0.3f) + .setInterpolator(mInterpolator) + .setDuration(500) + .setStartDelay(800) + .start() + } + + companion object { + internal val FLAVORS = intArrayOf( + 0xFF9C27B0.toInt(), 0xFFBA68C8.toInt(), // grape + 0xFFFF9800.toInt(), 0xFFFFB74D.toInt(), // orange + 0xFFF06292.toInt(), 0xFFF8BBD0.toInt(), // bubblegum + 0xFFAFB42B.toInt(), 0xFFCDDC39.toInt(), // lime + 0xFF795548.toInt(), 0xFFA1887F.toInt() + )// mystery flavor + + internal fun newColorIndex(): Int { + return 2 * (Math.random() * FLAVORS.size / 2).toInt() + } + } } diff --git a/app/src/main/java/io/neoterm/ui/crash/CrashActivity.kt b/app/src/main/java/io/neoterm/ui/crash/CrashActivity.kt index d0d6cff..fb8afa1 100644 --- a/app/src/main/java/io/neoterm/ui/crash/CrashActivity.kt +++ b/app/src/main/java/io/neoterm/ui/crash/CrashActivity.kt @@ -2,8 +2,8 @@ package io.neoterm.ui.crash import android.os.Build import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity import io.neoterm.R import java.io.ByteArrayOutputStream import java.io.PrintStream @@ -12,48 +12,48 @@ import java.io.PrintStream * @author kiva */ class CrashActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.ui_crash) - setSupportActionBar(findViewById(R.id.crash_toolbar)) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.ui_crash) + setSupportActionBar(findViewById(R.id.crash_toolbar)) - (findViewById(R.id.crash_model)).text = getString(R.string.crash_model, collectModelInfo()) - (findViewById(R.id.crash_app_version)).text = getString(R.string.crash_app, collectAppInfo()) - (findViewById(R.id.crash_details)).text = collectExceptionInfo() - } + (findViewById(R.id.crash_model)).text = getString(R.string.crash_model, collectModelInfo()) + (findViewById(R.id.crash_app_version)).text = getString(R.string.crash_app, collectAppInfo()) + (findViewById(R.id.crash_details)).text = collectExceptionInfo() + } - private fun collectExceptionInfo(): String { - val extra = intent.getSerializableExtra("exception") - if (extra != null && extra is Throwable) { - val byteArrayOutput = ByteArrayOutputStream() - val printStream = PrintStream(byteArrayOutput) - (extra.cause ?: extra).printStackTrace(printStream) - return byteArrayOutput.use { - byteArrayOutput.toString("utf-8") - } - } - return "are.you.kidding.me.NoExceptionFoundException: This is a bug, please contact developers!" + private fun collectExceptionInfo(): String { + val extra = intent.getSerializableExtra("exception") + if (extra != null && extra is Throwable) { + val byteArrayOutput = ByteArrayOutputStream() + val printStream = PrintStream(byteArrayOutput) + (extra.cause ?: extra).printStackTrace(printStream) + return byteArrayOutput.use { + byteArrayOutput.toString("utf-8") + } } + return "are.you.kidding.me.NoExceptionFoundException: This is a bug, please contact developers!" + } - private fun collectAppInfo(): String { - val pm = packageManager - val info = pm.getPackageInfo(packageName, 0) - return "${info.versionName} (${info.versionCode})" - } + private fun collectAppInfo(): String { + val pm = packageManager + val info = pm.getPackageInfo(packageName, 0) + return "${info.versionName} (${info.versionCode})" + } - private fun collectModelInfo(): String { - return "${Build.MODEL} (Android ${Build.VERSION.RELEASE} ${determineArchName()})" - } + private fun collectModelInfo(): String { + return "${Build.MODEL} (Android ${Build.VERSION.RELEASE} ${determineArchName()})" + } - private fun determineArchName(): String { - for (androidArch in Build.SUPPORTED_ABIS) { - when (androidArch) { - "arm64-v8a" -> return "aarch64" - "armeabi-v7a" -> return "arm" - "x86_64" -> return "x86_64" - "x86" -> return "i686" - } - } - return "Unknown Arch" + private fun determineArchName(): String { + for (androidArch in Build.SUPPORTED_ABIS) { + when (androidArch) { + "arm64-v8a" -> return "aarch64" + "armeabi-v7a" -> return "arm" + "x86_64" -> return "x86_64" + "x86" -> return "i686" + } } + return "Unknown Arch" + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/customize/BaseCustomizeActivity.kt b/app/src/main/java/io/neoterm/ui/customize/BaseCustomizeActivity.kt index be1226d..afb1010 100644 --- a/app/src/main/java/io/neoterm/ui/customize/BaseCustomizeActivity.kt +++ b/app/src/main/java/io/neoterm/ui/customize/BaseCustomizeActivity.kt @@ -1,9 +1,9 @@ package io.neoterm.ui.customize import android.annotation.SuppressLint +import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar -import android.view.MenuItem import io.neoterm.R import io.neoterm.backend.TerminalSession import io.neoterm.frontend.config.NeoTermPath @@ -19,41 +19,41 @@ import io.neoterm.utils.TerminalUtils */ @SuppressLint("Registered") open class BaseCustomizeActivity : AppCompatActivity() { - lateinit var terminalView: TerminalView - lateinit var viewClient: BasicViewClient - lateinit var sessionCallback: BasicSessionCallback - lateinit var session: TerminalSession - lateinit var extraKeysView: ExtraKeysView + lateinit var terminalView: TerminalView + lateinit var viewClient: BasicViewClient + lateinit var sessionCallback: BasicSessionCallback + lateinit var session: TerminalSession + lateinit var extraKeysView: ExtraKeysView - fun initCustomizationComponent(layoutId: Int) { - setContentView(layoutId) + fun initCustomizationComponent(layoutId: Int) { + setContentView(layoutId) - val toolbar = findViewById(R.id.custom_toolbar) - setSupportActionBar(toolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) + val toolbar = findViewById(R.id.custom_toolbar) + setSupportActionBar(toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) - terminalView = findViewById(R.id.terminal_view) - extraKeysView = findViewById(R.id.custom_extra_keys) - viewClient = BasicViewClient(terminalView) - sessionCallback = BasicSessionCallback(terminalView) - TerminalUtils.setupTerminalView(terminalView, viewClient) - TerminalUtils.setupExtraKeysView(extraKeysView) + terminalView = findViewById(R.id.terminal_view) + extraKeysView = findViewById(R.id.custom_extra_keys) + viewClient = BasicViewClient(terminalView) + sessionCallback = BasicSessionCallback(terminalView) + TerminalUtils.setupTerminalView(terminalView, viewClient) + TerminalUtils.setupExtraKeysView(extraKeysView) - val script = resources.getStringArray(R.array.custom_preview_script_colors) - val parameter = ShellParameter() - .executablePath("${NeoTermPath.USR_PATH}/bin/echo") - .arguments(arrayOf("echo", "-e", *script)) - .callback(sessionCallback) - .systemShell(false) + val script = resources.getStringArray(R.array.custom_preview_script_colors) + val parameter = ShellParameter() + .executablePath("${NeoTermPath.USR_PATH}/bin/echo") + .arguments(arrayOf("echo", "-e", *script)) + .callback(sessionCallback) + .systemShell(false) - session = TerminalUtils.createSession(this, parameter) - terminalView.attachSession(session) - } - - override fun onOptionsItemSelected(item: MenuItem?): Boolean { - when (item?.itemId) { - android.R.id.home -> finish() - } - return super.onOptionsItemSelected(item) + session = TerminalUtils.createSession(this, parameter) + terminalView.attachSession(session) + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + when (item?.itemId) { + android.R.id.home -> finish() } + return super.onOptionsItemSelected(item) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/customize/ColorSchemeActivity.kt b/app/src/main/java/io/neoterm/ui/customize/ColorSchemeActivity.kt index 73644ae..714215e 100644 --- a/app/src/main/java/io/neoterm/ui/customize/ColorSchemeActivity.kt +++ b/app/src/main/java/io/neoterm/ui/customize/ColorSchemeActivity.kt @@ -1,9 +1,6 @@ package io.neoterm.ui.customize -import androidx.appcompat.app.AlertDialog import android.os.Bundle -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import android.text.Editable import android.text.TextWatcher import android.view.KeyEvent @@ -13,6 +10,8 @@ import android.view.MenuItem import android.widget.EditText import android.widget.TextView import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.recyclerview.widget.LinearLayoutManager import com.github.wrdlbrnft.sortedlistadapter.SortedListAdapter import es.dmoral.coloromatic.ColorOMaticDialog import es.dmoral.coloromatic.IndicatorMode @@ -32,159 +31,159 @@ import io.neoterm.utils.TerminalUtils * @author kiva */ class ColorSchemeActivity : BaseCustomizeActivity() { - private val COMPARATOR = SortedListAdapter.ComparatorBuilder() - .setOrderForModel(ColorItem::class.java) { a, b -> - a.colorType.compareTo(b.colorType) - } - .build() + private val COMPARATOR = SortedListAdapter.ComparatorBuilder() + .setOrderForModel(ColorItem::class.java) { a, b -> + a.colorType.compareTo(b.colorType) + } + .build() - var changed = false - private lateinit var editingColorScheme: NeoColorScheme - lateinit var adapter: ColorItemAdapter + var changed = false + private lateinit var editingColorScheme: NeoColorScheme + lateinit var adapter: ColorItemAdapter - private val colorSchemeComponent = ComponentManager.getComponent() + private val colorSchemeComponent = ComponentManager.getComponent() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initCustomizationComponent(R.layout.ui_color_scheme) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initCustomizationComponent(R.layout.ui_color_scheme) - editingColorScheme = colorSchemeComponent.getCurrentColorScheme().copy() - editingColorScheme.colorName = "" + editingColorScheme = colorSchemeComponent.getCurrentColorScheme().copy() + editingColorScheme.colorName = "" - val terminalView = findViewById(R.id.terminal_view) - TerminalUtils.setupTerminalView(terminalView, null) + val terminalView = findViewById(R.id.terminal_view) + TerminalUtils.setupTerminalView(terminalView, null) - adapter = ColorItemAdapter(this, editingColorScheme, COMPARATOR, object : ColorItemAdapter.Listener { - override fun onModelClicked(model: ColorItem) { - showItemEditor(model) - } + adapter = ColorItemAdapter(this, editingColorScheme, COMPARATOR, object : ColorItemAdapter.Listener { + override fun onModelClicked(model: ColorItem) { + showItemEditor(model) + } + }) + val recyclerView = findViewById(R.id.custom_color_color_list) + recyclerView.setHasFixedSize(true) + recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.adapter = adapter + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_color_editor, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + when (item?.itemId) { + android.R.id.home -> finish() + R.id.action_done -> applyColorScheme(editingColorScheme) + } + return super.onOptionsItemSelected(item) + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + if (keyCode == KeyEvent.KEYCODE_BACK && event!!.action == KeyEvent.ACTION_DOWN && changed) { + AlertDialog.Builder(this) + .setMessage(getString(R.string.discard_changes)) + .setPositiveButton(R.string.save, { _, _ -> + applyColorScheme(editingColorScheme, true) }) - val recyclerView = findViewById(R.id.custom_color_color_list) - recyclerView.setHasFixedSize(true) - recyclerView.layoutManager = LinearLayoutManager(this) - recyclerView.adapter = adapter - } - - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.menu_color_editor, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem?): Boolean { - when (item?.itemId) { - android.R.id.home -> finish() - R.id.action_done -> applyColorScheme(editingColorScheme) - } - return super.onOptionsItemSelected(item) - } - - override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { - if (keyCode == KeyEvent.KEYCODE_BACK && event!!.action == KeyEvent.ACTION_DOWN && changed) { - AlertDialog.Builder(this) - .setMessage(getString(R.string.discard_changes)) - .setPositiveButton(R.string.save, { _, _ -> - applyColorScheme(editingColorScheme, true) - }) - .setNegativeButton(android.R.string.no, null) - .setNeutralButton(R.string.exit, { _, _ -> - finish() - }) - .show() - return true - } - return super.onKeyDown(keyCode, event) - } - - private fun showItemEditor(model: ColorItem) { - val view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null, false) - view.findViewById(R.id.dialog_edit_text_info).text = getString(R.string.input_new_value) - - val edit = view.findViewById(R.id.dialog_edit_text_editor) - edit.setText(model.colorValue) - if (model.colorValue.isNotEmpty()) { - edit.setTextColor(TerminalColors.parse(model.colorValue)) - } - edit.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(editable: Editable?) { - if (editable != null && editable.isNotEmpty()) { - val color = TerminalColors.parse(editable.toString()) - if (color != 0) { - edit.setTextColor(color) - } else { - edit.setTextColor(resources.getColor(R.color.textColor)) - } - } - } - - override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { - } - - override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { - } + .setNegativeButton(android.R.string.no, null) + .setNeutralButton(R.string.exit, { _, _ -> + finish() }) + .show() + return true + } + return super.onKeyDown(keyCode, event) + } - val applyColor: (newColor: String) -> Unit = { newColor -> - model.colorValue = newColor - adapter.notifyItemChanged(adapter.colorList.indexOf(model)) + private fun showItemEditor(model: ColorItem) { + val view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null, false) + view.findViewById(R.id.dialog_edit_text_info).text = getString(R.string.input_new_value) - editingColorScheme.setColor(model.colorType, model.colorValue) - colorSchemeComponent.applyColorScheme(terminalView, null, editingColorScheme) - changed = true + val edit = view.findViewById(R.id.dialog_edit_text_editor) + edit.setText(model.colorValue) + if (model.colorValue.isNotEmpty()) { + edit.setTextColor(TerminalColors.parse(model.colorValue)) + } + edit.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(editable: Editable?) { + if (editable != null && editable.isNotEmpty()) { + val color = TerminalColors.parse(editable.toString()) + if (color != 0) { + edit.setTextColor(color) + } else { + edit.setTextColor(resources.getColor(R.color.textColor)) + } } + } - AlertDialog.Builder(this) - .setTitle(model.colorName) - .setView(view) - .setNegativeButton(android.R.string.no, null) - .setPositiveButton(android.R.string.yes, { _, _ -> - applyColor(edit.text.toString()); - }) - .setNeutralButton(R.string.select_new_value, { _, _ -> - ColorOMaticDialog.Builder() - .initialColor(TerminalColors.parse(model.colorValue)) - .colorMode(ColorMode.RGB) - .indicatorMode(IndicatorMode.HEX) - .onColorSelected { newColor -> - applyColor("#${Integer.toHexString(newColor).substring(2)}") - } - .showColorIndicator(true) - .create() - .show(supportFragmentManager, "ColorOMaticDialog") - }) - .show() + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + }) + + val applyColor: (newColor: String) -> Unit = { newColor -> + model.colorValue = newColor + adapter.notifyItemChanged(adapter.colorList.indexOf(model)) + + editingColorScheme.setColor(model.colorType, model.colorValue) + colorSchemeComponent.applyColorScheme(terminalView, null, editingColorScheme) + changed = true } - private fun applyColorScheme(colorScheme: NeoColorScheme, finishAfter: Boolean = false) { - if (colorScheme.colorName.isEmpty()) { - val view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null, false) - view.findViewById(R.id.dialog_edit_text_info).text = getString(R.string.save_color_info) + AlertDialog.Builder(this) + .setTitle(model.colorName) + .setView(view) + .setNegativeButton(android.R.string.no, null) + .setPositiveButton(android.R.string.yes, { _, _ -> + applyColor(edit.text.toString()); + }) + .setNeutralButton(R.string.select_new_value, { _, _ -> + ColorOMaticDialog.Builder() + .initialColor(TerminalColors.parse(model.colorValue)) + .colorMode(ColorMode.RGB) + .indicatorMode(IndicatorMode.HEX) + .onColorSelected { newColor -> + applyColor("#${Integer.toHexString(newColor).substring(2)}") + } + .showColorIndicator(true) + .create() + .show(supportFragmentManager, "ColorOMaticDialog") + }) + .show() + } - val edit = view.findViewById(R.id.dialog_edit_text_editor) - edit.setText(getString(R.string.save_color_scheme_name_template)) + private fun applyColorScheme(colorScheme: NeoColorScheme, finishAfter: Boolean = false) { + if (colorScheme.colorName.isEmpty()) { + val view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null, false) + view.findViewById(R.id.dialog_edit_text_info).text = getString(R.string.save_color_info) - AlertDialog.Builder(this) - .setTitle(R.string.save_color) - .setView(view) - .setPositiveButton(android.R.string.yes, { _, _ -> - colorScheme.colorName = edit.text.toString() - applyColorScheme(colorScheme, finishAfter) - }) - .setNegativeButton(android.R.string.no, null) - .show() - } else { - try { - colorSchemeComponent.saveColorScheme(colorScheme) - colorSchemeComponent.reloadColorSchemes() - colorSchemeComponent.setCurrentColorScheme(colorScheme) - changed = false + val edit = view.findViewById(R.id.dialog_edit_text_editor) + edit.setText(getString(R.string.save_color_scheme_name_template)) - Toast.makeText(this, R.string.done, Toast.LENGTH_SHORT).show() - if (finishAfter) { - finish() - } - } catch (e: Exception) { - Toast.makeText(this, getString(R.string.error) + ": ${e.localizedMessage}", Toast.LENGTH_LONG).show() - } + AlertDialog.Builder(this) + .setTitle(R.string.save_color) + .setView(view) + .setPositiveButton(android.R.string.yes, { _, _ -> + colorScheme.colorName = edit.text.toString() + applyColorScheme(colorScheme, finishAfter) + }) + .setNegativeButton(android.R.string.no, null) + .show() + } else { + try { + colorSchemeComponent.saveColorScheme(colorScheme) + colorSchemeComponent.reloadColorSchemes() + colorSchemeComponent.setCurrentColorScheme(colorScheme) + changed = false + + Toast.makeText(this, R.string.done, Toast.LENGTH_SHORT).show() + if (finishAfter) { + finish() } + } catch (e: Exception) { + Toast.makeText(this, getString(R.string.error) + ": ${e.localizedMessage}", Toast.LENGTH_LONG).show() + } } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/customize/CustomizeActivity.kt b/app/src/main/java/io/neoterm/ui/customize/CustomizeActivity.kt index 161b74b..45d1c2d 100644 --- a/app/src/main/java/io/neoterm/ui/customize/CustomizeActivity.kt +++ b/app/src/main/java/io/neoterm/ui/customize/CustomizeActivity.kt @@ -22,132 +22,132 @@ import java.nio.file.Paths * @author kiva */ class CustomizeActivity : BaseCustomizeActivity() { - private val REQUEST_SELECT_FONT = 22222 - private val REQUEST_SELECT_COLOR = 22223 + private val REQUEST_SELECT_FONT = 22222 + private val REQUEST_SELECT_COLOR = 22223 - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initCustomizationComponent(R.layout.ui_customize) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initCustomizationComponent(R.layout.ui_customize) - findViewById(R.id.custom_install_font_button).setOnClickListener { - val intent = Intent() - intent.action = Intent.ACTION_GET_CONTENT - intent.type = "*/*" - startActivityForResult(Intent.createChooser(intent, getString(R.string.install_font)), REQUEST_SELECT_FONT) + findViewById(R.id.custom_install_font_button).setOnClickListener { + val intent = Intent() + intent.action = Intent.ACTION_GET_CONTENT + intent.type = "*/*" + startActivityForResult(Intent.createChooser(intent, getString(R.string.install_font)), REQUEST_SELECT_FONT) + } + + findViewById(R.id.custom_install_color_button).setOnClickListener { + val intent = Intent() + intent.action = Intent.ACTION_GET_CONTENT + intent.type = "*/*" + startActivityForResult( + Intent.createChooser(intent, getString(R.string.install_color)), + REQUEST_SELECT_COLOR + ) + } + } + + private fun setupSpinners() { + val fontComponent = ComponentManager.getComponent() + val colorSchemeComponent = ComponentManager.getComponent() + + setupSpinner(R.id.custom_font_spinner, fontComponent.getFontNames(), + fontComponent.getCurrentFontName(), object : AdapterView.OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) { } - findViewById(R.id.custom_install_color_button).setOnClickListener { - val intent = Intent() - intent.action = Intent.ACTION_GET_CONTENT - intent.type = "*/*" - startActivityForResult( - Intent.createChooser(intent, getString(R.string.install_color)), - REQUEST_SELECT_COLOR - ) + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + val fontName = parent!!.adapter!!.getItem(position) as String + val font = fontComponent.getFont(fontName) + fontComponent.applyFont(terminalView, extraKeysView, font) + fontComponent.setCurrentFont(fontName) } - } + }) - private fun setupSpinners() { - val fontComponent = ComponentManager.getComponent() - val colorSchemeComponent = ComponentManager.getComponent() - - setupSpinner(R.id.custom_font_spinner, fontComponent.getFontNames(), - fontComponent.getCurrentFontName(), object : AdapterView.OnItemSelectedListener { - override fun onNothingSelected(parent: AdapterView<*>?) { - } - - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - val fontName = parent!!.adapter!!.getItem(position) as String - val font = fontComponent.getFont(fontName) - fontComponent.applyFont(terminalView, extraKeysView, font) - fontComponent.setCurrentFont(fontName) - } - }) - - val colorData = listOf( - getString(R.string.new_color_scheme), - *colorSchemeComponent.getColorSchemeNames().toTypedArray() - ) - setupSpinner(R.id.custom_color_spinner, colorData, - colorSchemeComponent.getCurrentColorSchemeName(), object : AdapterView.OnItemSelectedListener { - override fun onNothingSelected(parent: AdapterView<*>?) { - } - - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - if (position == 0) { - val intent = Intent(this@CustomizeActivity, ColorSchemeActivity::class.java) - startActivity(intent) - return - } - val colorName = parent!!.adapter!!.getItem(position) as String - val color = colorSchemeComponent.getColorScheme(colorName) - colorSchemeComponent.applyColorScheme(terminalView, extraKeysView, color) - colorSchemeComponent.setCurrentColorScheme(colorName) - } - }) - } - - private fun setupSpinner( - id: Int, - data: List, - selected: String, - listener: AdapterView.OnItemSelectedListener - ): Spinner { - val spinner = findViewById(id) - val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, data) - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - spinner.adapter = adapter - spinner.onItemSelectedListener = listener - spinner.setSelection(if (data.contains(selected)) data.indexOf(selected) else 0) - return spinner - } - - override fun onResume() { - super.onResume() - setupSpinners() - } - - override fun onDestroy() { - super.onDestroy() - session.finishIfRunning() - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (resultCode == RESULT_OK && data != null) { - val selected = MediaUtils.getPath(this, data.data) - if (selected != null && selected.isNotEmpty()) { - when (requestCode) { - REQUEST_SELECT_FONT -> installFont(selected) - REQUEST_SELECT_COLOR -> installColor(selected) - } - } + val colorData = listOf( + getString(R.string.new_color_scheme), + *colorSchemeComponent.getColorSchemeNames().toTypedArray() + ) + setupSpinner(R.id.custom_color_spinner, colorData, + colorSchemeComponent.getCurrentColorSchemeName(), object : AdapterView.OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) { } - super.onActivityResult(requestCode, resultCode, data) - } - private fun installColor(selected: String) { - installFileTo(selected, NeoTermPath.COLORS_PATH) - setupSpinners() - } - - private fun installFont(selected: String) { - installFileTo(selected, NeoTermPath.FONT_PATH) - setupSpinners() - } - - private fun installFileTo(file: String, targetDir: String) { - kotlin.runCatching { - val source = File(file) - Files.copy(source.toPath(), Paths.get(targetDir, source.name)) - }.onFailure { - Toast.makeText(this, getString(R.string.error) + ": ${it.localizedMessage}", Toast.LENGTH_LONG).show() + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + if (position == 0) { + val intent = Intent(this@CustomizeActivity, ColorSchemeActivity::class.java) + startActivity(intent) + return + } + val colorName = parent!!.adapter!!.getItem(position) as String + val color = colorSchemeComponent.getColorScheme(colorName) + colorSchemeComponent.applyColorScheme(terminalView, extraKeysView, color) + colorSchemeComponent.setCurrentColorScheme(colorName) } - } + }) + } - override fun onOptionsItemSelected(item: MenuItem?): Boolean { - when (item?.itemId) { - android.R.id.home -> finish() + private fun setupSpinner( + id: Int, + data: List, + selected: String, + listener: AdapterView.OnItemSelectedListener + ): Spinner { + val spinner = findViewById(id) + val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, data) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + spinner.adapter = adapter + spinner.onItemSelectedListener = listener + spinner.setSelection(if (data.contains(selected)) data.indexOf(selected) else 0) + return spinner + } + + override fun onResume() { + super.onResume() + setupSpinners() + } + + override fun onDestroy() { + super.onDestroy() + session.finishIfRunning() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode == RESULT_OK && data != null) { + val selected = MediaUtils.getPath(this, data.data) + if (selected != null && selected.isNotEmpty()) { + when (requestCode) { + REQUEST_SELECT_FONT -> installFont(selected) + REQUEST_SELECT_COLOR -> installColor(selected) } - return super.onOptionsItemSelected(item) + } } + super.onActivityResult(requestCode, resultCode, data) + } + + private fun installColor(selected: String) { + installFileTo(selected, NeoTermPath.COLORS_PATH) + setupSpinners() + } + + private fun installFont(selected: String) { + installFileTo(selected, NeoTermPath.FONT_PATH) + setupSpinners() + } + + private fun installFileTo(file: String, targetDir: String) { + kotlin.runCatching { + val source = File(file) + Files.copy(source.toPath(), Paths.get(targetDir, source.name)) + }.onFailure { + Toast.makeText(this, getString(R.string.error) + ": ${it.localizedMessage}", Toast.LENGTH_LONG).show() + } + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + when (item?.itemId) { + android.R.id.home -> finish() + } + return super.onOptionsItemSelected(item) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/customize/adapter/ColorItemAdapter.kt b/app/src/main/java/io/neoterm/ui/customize/adapter/ColorItemAdapter.kt index b316216..7359d16 100644 --- a/app/src/main/java/io/neoterm/ui/customize/adapter/ColorItemAdapter.kt +++ b/app/src/main/java/io/neoterm/ui/customize/adapter/ColorItemAdapter.kt @@ -9,34 +9,41 @@ import io.neoterm.R import io.neoterm.component.colorscheme.NeoColorScheme import io.neoterm.ui.customize.adapter.holder.ColorItemViewHolder import io.neoterm.ui.customize.model.ColorItem -import java.util.* /** * @author kiva */ -class ColorItemAdapter(context: Context, initColorScheme: NeoColorScheme, comparator: Comparator, private val listener: ColorItemAdapter.Listener) - : SortedListAdapter(context, ColorItem::class.java, comparator), FastScrollRecyclerView.SectionedAdapter { +class ColorItemAdapter( + context: Context, + initColorScheme: NeoColorScheme, + comparator: Comparator, + private val listener: ColorItemAdapter.Listener +) : SortedListAdapter(context, ColorItem::class.java, comparator), FastScrollRecyclerView.SectionedAdapter { - val colorList = mutableListOf() + val colorList = mutableListOf() - init { - (NeoColorScheme.COLOR_TYPE_BEGIN..NeoColorScheme.COLOR_TYPE_END) - .forEach { - colorList.add(ColorItem(it, initColorScheme.getColor(it) ?: "")) - } - edit().add(colorList).commit() - } + init { + (NeoColorScheme.COLOR_TYPE_BEGIN..NeoColorScheme.COLOR_TYPE_END) + .forEach { + colorList.add(ColorItem(it, initColorScheme.getColor(it) ?: "")) + } + edit().add(colorList).commit() + } - interface Listener { - fun onModelClicked(model: ColorItem) - } + interface Listener { + fun onModelClicked(model: ColorItem) + } - override fun getSectionName(position: Int): String { - return colorList[position].colorName[0].toString() - } + override fun getSectionName(position: Int): String { + return colorList[position].colorName[0].toString() + } - override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup, viewType: Int): SortedListAdapter.ViewHolder { - val rootView = inflater.inflate(R.layout.item_color, parent, false) - return ColorItemViewHolder(rootView, listener) - } + override fun onCreateViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + viewType: Int + ): SortedListAdapter.ViewHolder { + val rootView = inflater.inflate(R.layout.item_color, parent, false) + return ColorItemViewHolder(rootView, listener) + } } diff --git a/app/src/main/java/io/neoterm/ui/customize/adapter/holder/ColorItemViewHolder.kt b/app/src/main/java/io/neoterm/ui/customize/adapter/holder/ColorItemViewHolder.kt index 67d683c..4e8e0ad 100644 --- a/app/src/main/java/io/neoterm/ui/customize/adapter/holder/ColorItemViewHolder.kt +++ b/app/src/main/java/io/neoterm/ui/customize/adapter/holder/ColorItemViewHolder.kt @@ -8,19 +8,20 @@ import io.neoterm.backend.TerminalColors import io.neoterm.ui.customize.adapter.ColorItemAdapter import io.neoterm.ui.customize.model.ColorItem -class ColorItemViewHolder(private val rootView: View, private val listener: ColorItemAdapter.Listener) : SortedListAdapter.ViewHolder(rootView) { - private val colorItemName: TextView = rootView.findViewById(R.id.color_item_name) - private val colorItemDesc: TextView = rootView.findViewById(R.id.color_item_description) - private val colorView: View = rootView.findViewById(R.id.color_item_view) +class ColorItemViewHolder(private val rootView: View, private val listener: ColorItemAdapter.Listener) : + SortedListAdapter.ViewHolder(rootView) { + private val colorItemName: TextView = rootView.findViewById(R.id.color_item_name) + private val colorItemDesc: TextView = rootView.findViewById(R.id.color_item_description) + private val colorView: View = rootView.findViewById(R.id.color_item_view) - override fun performBind(item: ColorItem) { - rootView.setOnClickListener { listener.onModelClicked(item) } - colorItemName.text = item.colorName - colorItemDesc.text = item.colorValue - if (item.colorValue.isNotEmpty()) { - val color = TerminalColors.parse(item.colorValue) - colorView.setBackgroundColor(color) - colorItemDesc.setTextColor(color) - } + override fun performBind(item: ColorItem) { + rootView.setOnClickListener { listener.onModelClicked(item) } + colorItemName.text = item.colorName + colorItemDesc.text = item.colorValue + if (item.colorValue.isNotEmpty()) { + val color = TerminalColors.parse(item.colorValue) + colorView.setBackgroundColor(color) + colorItemDesc.setTextColor(color) } + } } diff --git a/app/src/main/java/io/neoterm/ui/customize/model/ColorItem.kt b/app/src/main/java/io/neoterm/ui/customize/model/ColorItem.kt index 92789b3..4d3468c 100644 --- a/app/src/main/java/io/neoterm/ui/customize/model/ColorItem.kt +++ b/app/src/main/java/io/neoterm/ui/customize/model/ColorItem.kt @@ -9,19 +9,19 @@ import io.neoterm.component.colorscheme.NeoColorScheme * @author kiva */ class ColorItem(var colorType: Int, var colorValue: String) : SortedListAdapter.ViewModel { - override fun isSameModelAs(t: T): Boolean { - if (t is ColorItem) { - return t.colorName == colorName - && t.colorValue == colorValue - && t.colorType == colorType - } - return false + override fun isSameModelAs(t: T): Boolean { + if (t is ColorItem) { + return t.colorName == colorName + && t.colorValue == colorValue + && t.colorType == colorType } + return false + } - override fun isContentTheSameAs(t: T): Boolean { - return isSameModelAs(t) - } + override fun isContentTheSameAs(t: T): Boolean { + return isSameModelAs(t) + } - var colorName = App.get().resources - .getStringArray(R.array.color_item_names)[colorType - NeoColorScheme.COLOR_TYPE_BEGIN] + var colorName = App.get().resources + .getStringArray(R.array.color_item_names)[colorType - NeoColorScheme.COLOR_TYPE_BEGIN] } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/pm/PackageManagerActivity.kt b/app/src/main/java/io/neoterm/ui/pm/PackageManagerActivity.kt index 6299525..7016102 100644 --- a/app/src/main/java/io/neoterm/ui/pm/PackageManagerActivity.kt +++ b/app/src/main/java/io/neoterm/ui/pm/PackageManagerActivity.kt @@ -2,19 +2,18 @@ package io.neoterm.ui.pm import android.annotation.SuppressLint import android.os.Bundle -import androidx.core.view.MenuItemCompat -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import androidx.appcompat.widget.SearchView -import androidx.appcompat.widget.Toolbar import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem import android.widget.EditText import android.widget.TextView import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SearchView +import androidx.appcompat.widget.Toolbar +import androidx.core.view.MenuItemCompat +import androidx.recyclerview.widget.LinearLayoutManager import com.github.wrdlbrnft.sortedlistadapter.SortedListAdapter import io.neoterm.R import io.neoterm.backend.TerminalSession @@ -33,245 +32,249 @@ import io.neoterm.utils.PackageUtils */ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListener, SortedListAdapter.Callback { - private val COMPARATOR = SortedListAdapter.ComparatorBuilder() - .setOrderForModel(PackageModel::class.java) { a, b -> - a.packageInfo.packageName!!.compareTo(b.packageInfo.packageName!!) - } - .build() + private val COMPARATOR = SortedListAdapter.ComparatorBuilder() + .setOrderForModel(PackageModel::class.java) { a, b -> + a.packageInfo.packageName!!.compareTo(b.packageInfo.packageName!!) + } + .build() - lateinit var recyclerView: androidx.recyclerview.widget.RecyclerView - lateinit var adapter: PackageAdapter - lateinit var models: ArrayList + lateinit var recyclerView: androidx.recyclerview.widget.RecyclerView + lateinit var adapter: PackageAdapter + lateinit var models: ArrayList - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.ui_pm_single_tab) - val toolbar = findViewById(R.id.pm_toolbar) - setSupportActionBar(toolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.ui_pm_single_tab) + val toolbar = findViewById(R.id.pm_toolbar) + setSupportActionBar(toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) - recyclerView = findViewById(R.id.pm_package_list) - recyclerView.setHasFixedSize(true) - adapter = PackageAdapter(this, COMPARATOR, object : PackageAdapter.Listener { - override fun onModelClicked(model: PackageModel) { - AlertDialog.Builder(this@PackageManagerActivity) - .setTitle(model.packageInfo.packageName) - .setMessage(model.getPackageDetails(this@PackageManagerActivity)) - .setPositiveButton(R.string.install, { _, _ -> - installPackage(model.packageInfo.packageName) - }) - .setNegativeButton(android.R.string.no, null) - .show() - } + recyclerView = findViewById(R.id.pm_package_list) + recyclerView.setHasFixedSize(true) + adapter = PackageAdapter(this, COMPARATOR, object : PackageAdapter.Listener { + override fun onModelClicked(model: PackageModel) { + AlertDialog.Builder(this@PackageManagerActivity) + .setTitle(model.packageInfo.packageName) + .setMessage(model.getPackageDetails(this@PackageManagerActivity)) + .setPositiveButton(R.string.install, { _, _ -> + installPackage(model.packageInfo.packageName) + }) + .setNegativeButton(android.R.string.no, null) + .show() + } + }) + adapter.addCallback(this) + + recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.adapter = adapter + + models = ArrayList() + refreshPackageList() + } + + private fun installPackage(packageName: String?) { + if (packageName != null) { + TerminalDialog(this@PackageManagerActivity) + .execute( + NeoTermPath.APT_BIN_PATH, + arrayOf("apt", "install", "-y", packageName) + ) + .onFinish(object : TerminalDialog.SessionFinishedCallback { + override fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?) { + dialog.setTitle(getString(R.string.done)) + } }) - adapter.addCallback(this) - - recyclerView.layoutManager = LinearLayoutManager(this) - recyclerView.adapter = adapter - - models = ArrayList() - refreshPackageList() + .imeEnabled(true) + .show("Installing $packageName") + Toast.makeText(this, R.string.installing_topic, Toast.LENGTH_LONG).show() } + } - private fun installPackage(packageName: String?) { - if (packageName != null) { - TerminalDialog(this@PackageManagerActivity) - .execute(NeoTermPath.APT_BIN_PATH, - arrayOf("apt", "install", "-y", packageName)) - .onFinish(object : TerminalDialog.SessionFinishedCallback { - override fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?) { - dialog.setTitle(getString(R.string.done)) - } - }) - .imeEnabled(true) - .show("Installing $packageName") - Toast.makeText(this, R.string.installing_topic, Toast.LENGTH_LONG).show() + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_pm, menu) + val searchItem = menu!!.findItem(R.id.action_search) + val searchView = MenuItemCompat.getActionView(searchItem) as SearchView + searchView.setOnQueryTextListener(this) + return true + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + when (item?.itemId) { + android.R.id.home -> finish() + R.id.action_source -> changeSource() + R.id.action_update_and_refresh -> executeAptUpdate() + R.id.action_refresh -> refreshPackageList() + R.id.action_upgrade -> executeAptUpgrade() + } + return super.onOptionsItemSelected(item) + } + + private fun changeSource() { + val sourceManager = ComponentManager.getComponent().sourceManager + val sourceList = sourceManager.getAllSources() + + AlertDialog.Builder(this) + .setTitle(R.string.pref_package_source) + .setMultiChoiceItems(sourceList.map { "${it.url} :: ${it.repo}" }.toTypedArray(), + sourceList.map { it.enabled }.toBooleanArray(), { dialog, which, isChecked -> + sourceList[which].enabled = isChecked + }) + .setPositiveButton(android.R.string.yes, { _, _ -> + changeSourceInternal(sourceManager, sourceList) + }) + .setNeutralButton(R.string.new_source, { _, _ -> + changeSourceToUserInput(sourceManager) + }) + .setNegativeButton(android.R.string.no, null) + .show() + } + + @SuppressLint("SetTextI18n") + private fun changeSourceToUserInput(sourceManager: SourceManager) { + val view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_two_text, null, false) + view.findViewById(R.id.dialog_edit_text_info).text = getString(R.string.input_new_source_url) + view.findViewById(R.id.dialog_edit_text2_info).text = getString(R.string.input_new_source_repo) + + val urlEditor = view.findViewById(R.id.dialog_edit_text_editor) + val repoEditor = view.findViewById(R.id.dialog_edit_text2_editor) + repoEditor.setText("stable main") + + AlertDialog.Builder(this) + .setTitle(R.string.pref_package_source) + .setView(view) + .setNegativeButton(android.R.string.no, null) + .setPositiveButton(android.R.string.yes, { _, _ -> + val url = urlEditor.text.toString() + val repo = repoEditor.text.toString() + var errored = false + if (url.trim().isEmpty()) { + urlEditor.error = getString(R.string.error_new_source_url) + errored = true } - } - - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.menu_pm, menu) - val searchItem = menu!!.findItem(R.id.action_search) - val searchView = MenuItemCompat.getActionView(searchItem) as SearchView - searchView.setOnQueryTextListener(this) - return true - } - - override fun onOptionsItemSelected(item: MenuItem?): Boolean { - when (item?.itemId) { - android.R.id.home -> finish() - R.id.action_source -> changeSource() - R.id.action_update_and_refresh -> executeAptUpdate() - R.id.action_refresh -> refreshPackageList() - R.id.action_upgrade -> executeAptUpgrade() + if (repo.trim().isEmpty()) { + repoEditor.error = getString(R.string.error_new_source_repo) + errored = true } - return super.onOptionsItemSelected(item) - } - - private fun changeSource() { - val sourceManager = ComponentManager.getComponent().sourceManager - val sourceList = sourceManager.getAllSources() - - AlertDialog.Builder(this) - .setTitle(R.string.pref_package_source) - .setMultiChoiceItems(sourceList.map { "${it.url} :: ${it.repo}" }.toTypedArray(), - sourceList.map { it.enabled }.toBooleanArray(), { dialog, which, isChecked -> - sourceList[which].enabled = isChecked - }) - .setPositiveButton(android.R.string.yes, { _, _ -> - changeSourceInternal(sourceManager, sourceList) - }) - .setNeutralButton(R.string.new_source, { _, _ -> - changeSourceToUserInput(sourceManager) - }) - .setNegativeButton(android.R.string.no, null) - .show() - } - - @SuppressLint("SetTextI18n") - private fun changeSourceToUserInput(sourceManager: SourceManager) { - val view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_two_text, null, false) - view.findViewById(R.id.dialog_edit_text_info).text = getString(R.string.input_new_source_url) - view.findViewById(R.id.dialog_edit_text2_info).text = getString(R.string.input_new_source_repo) - - val urlEditor = view.findViewById(R.id.dialog_edit_text_editor) - val repoEditor = view.findViewById(R.id.dialog_edit_text2_editor) - repoEditor.setText("stable main") - - AlertDialog.Builder(this) - .setTitle(R.string.pref_package_source) - .setView(view) - .setNegativeButton(android.R.string.no, null) - .setPositiveButton(android.R.string.yes, { _, _ -> - val url = urlEditor.text.toString() - val repo = repoEditor.text.toString() - var errored = false - if (url.trim().isEmpty()) { - urlEditor.error = getString(R.string.error_new_source_url) - errored = true - } - if (repo.trim().isEmpty()) { - repoEditor.error = getString(R.string.error_new_source_repo) - errored = true - } - if (errored) { - return@setPositiveButton - } - val source = urlEditor.text.toString() - sourceManager.addSource(source, repo, true) - postChangeSource(sourceManager) - }) - .show() - } - - private fun changeSourceInternal(sourceManager: SourceManager, source: List) { - sourceManager.updateAll(source) + if (errored) { + return@setPositiveButton + } + val source = urlEditor.text.toString() + sourceManager.addSource(source, repo, true) postChangeSource(sourceManager) - } + }) + .show() + } - private fun postChangeSource(sourceManager: SourceManager) { - sourceManager.applyChanges() - NeoPreference.store(R.string.key_package_source, sourceManager.getMainPackageSource()) - SourceHelper.syncSource(sourceManager) - executeAptUpdate() - } + private fun changeSourceInternal(sourceManager: SourceManager, source: List) { + sourceManager.updateAll(source) + postChangeSource(sourceManager) + } - private fun executeAptUpdate() { - PackageUtils.apt(this, "update", null, { exitStatus, dialog -> - if (exitStatus != 0) { - dialog.setTitle(getString(R.string.error)) - return@apt - } - Toast.makeText(this, R.string.apt_update_ok, Toast.LENGTH_SHORT).show() - dialog.dismiss() - refreshPackageList() - }) - } + private fun postChangeSource(sourceManager: SourceManager) { + sourceManager.applyChanges() + NeoPreference.store(R.string.key_package_source, sourceManager.getMainPackageSource()) + SourceHelper.syncSource(sourceManager) + executeAptUpdate() + } - private fun executeAptUpgrade() { - PackageUtils.apt(this, "update", null, { exitStatus, dialog -> - if (exitStatus != 0) { - dialog.setTitle(getString(R.string.error)) - return@apt - } - dialog.dismiss() + private fun executeAptUpdate() { + PackageUtils.apt(this, "update", null, { exitStatus, dialog -> + if (exitStatus != 0) { + dialog.setTitle(getString(R.string.error)) + return@apt + } + Toast.makeText(this, R.string.apt_update_ok, Toast.LENGTH_SHORT).show() + dialog.dismiss() + refreshPackageList() + }) + } - PackageUtils.apt(this, "upgrade", arrayOf("-y"), out@ { exitStatus, dialog -> - if (exitStatus != 0) { - dialog.setTitle(getString(R.string.error)) - return@out - } - Toast.makeText(this, R.string.apt_upgrade_ok, Toast.LENGTH_SHORT).show() - dialog.dismiss() - }) - }) - } + private fun executeAptUpgrade() { + PackageUtils.apt(this, "update", null, { exitStatus, dialog -> + if (exitStatus != 0) { + dialog.setTitle(getString(R.string.error)) + return@apt + } + dialog.dismiss() - private fun refreshPackageList() { - models.clear() - Thread { - val pm = ComponentManager.getComponent() - val sourceFiles = SourceHelper.detectSourceFiles() - - pm.clearPackages() - sourceFiles.forEach { pm.reloadPackages(it, false) } - pm.packages.values.mapTo(models, { PackageModel(it) }) - - this@PackageManagerActivity.runOnUiThread { - adapter.edit() - .replaceAll(models) - .commit() - if (models.isEmpty()) { - Toast.makeText(this@PackageManagerActivity, R.string.package_list_empty, Toast.LENGTH_SHORT).show() - changeSource() - } - } - }.start() - } - - private fun sortDistance(models: List, query: String, - mapper: (NeoPackageInfo) -> String): List> { - return models - .map({ - Pair(it, StringDistance.distance(mapper(it.packageInfo).toLowerCase(), query.toLowerCase())) - }) - .sortedWith(Comparator { l, r -> r.second.compareTo(l.second) }) - .toList() - } - - private fun filter(models: List, query: String): List { - val filteredModelList = mutableListOf() - val prepared = models.filter { - it.packageInfo.packageName!!.contains(query, true) - || it.packageInfo.description!!.contains(query, true) + PackageUtils.apt(this, "upgrade", arrayOf("-y"), out@{ exitStatus, dialog -> + if (exitStatus != 0) { + dialog.setTitle(getString(R.string.error)) + return@out } + Toast.makeText(this, R.string.apt_upgrade_ok, Toast.LENGTH_SHORT).show() + dialog.dismiss() + }) + }) + } - sortDistance(prepared, query, { it.packageName!! }).mapTo(filteredModelList, { it.first }) - sortDistance(prepared, query, { it.description!! }).mapTo(filteredModelList, { it.first }) - return filteredModelList - } + private fun refreshPackageList() { + models.clear() + Thread { + val pm = ComponentManager.getComponent() + val sourceFiles = SourceHelper.detectSourceFiles() - override fun onQueryTextSubmit(text: String?): Boolean { - return false - } + pm.clearPackages() + sourceFiles.forEach { pm.reloadPackages(it, false) } + pm.packages.values.mapTo(models, { PackageModel(it) }) - override fun onQueryTextChange(text: String?): Boolean { - if (text != null) { - val filteredModelList = filter(models, text) - adapter.edit() - .replaceAll(filteredModelList) - .commit() + this@PackageManagerActivity.runOnUiThread { + adapter.edit() + .replaceAll(models) + .commit() + if (models.isEmpty()) { + Toast.makeText(this@PackageManagerActivity, R.string.package_list_empty, Toast.LENGTH_SHORT).show() + changeSource() } - return true + } + }.start() + } + + private fun sortDistance( + models: List, query: String, + mapper: (NeoPackageInfo) -> String + ): List> { + return models + .map({ + Pair(it, StringDistance.distance(mapper(it.packageInfo).toLowerCase(), query.toLowerCase())) + }) + .sortedWith(Comparator { l, r -> r.second.compareTo(l.second) }) + .toList() + } + + private fun filter(models: List, query: String): List { + val filteredModelList = mutableListOf() + val prepared = models.filter { + it.packageInfo.packageName!!.contains(query, true) + || it.packageInfo.description!!.contains(query, true) } - override fun onEditStarted() { - recyclerView.animate().alpha(0.5f) - } + sortDistance(prepared, query, { it.packageName!! }).mapTo(filteredModelList, { it.first }) + sortDistance(prepared, query, { it.description!! }).mapTo(filteredModelList, { it.first }) + return filteredModelList + } - override fun onEditFinished() { - recyclerView.scrollToPosition(0) - recyclerView.animate().alpha(1.0f) + override fun onQueryTextSubmit(text: String?): Boolean { + return false + } + + override fun onQueryTextChange(text: String?): Boolean { + if (text != null) { + val filteredModelList = filter(models, text) + adapter.edit() + .replaceAll(filteredModelList) + .commit() } + return true + } + + override fun onEditStarted() { + recyclerView.animate().alpha(0.5f) + } + + override fun onEditFinished() { + recyclerView.scrollToPosition(0) + recyclerView.animate().alpha(1.0f) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/pm/adapter/PackageAdapter.kt b/app/src/main/java/io/neoterm/ui/pm/adapter/PackageAdapter.kt index 762b8c0..2086640 100755 --- a/app/src/main/java/io/neoterm/ui/pm/adapter/PackageAdapter.kt +++ b/app/src/main/java/io/neoterm/ui/pm/adapter/PackageAdapter.kt @@ -8,20 +8,28 @@ import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView import io.neoterm.R import io.neoterm.ui.pm.adapter.holder.PackageViewHolder import io.neoterm.ui.pm.model.PackageModel -import java.util.* -class PackageAdapter(context: Context, comparator: Comparator, private val listener: PackageAdapter.Listener) : SortedListAdapter(context, PackageModel::class.java, comparator), FastScrollRecyclerView.SectionedAdapter { +class PackageAdapter( + context: Context, + comparator: Comparator, + private val listener: PackageAdapter.Listener +) : SortedListAdapter(context, PackageModel::class.java, comparator), + FastScrollRecyclerView.SectionedAdapter { - override fun getSectionName(position: Int): String { - return getItem(position).packageInfo.packageName?.substring(0, 1) ?: "#" - } + override fun getSectionName(position: Int): String { + return getItem(position).packageInfo.packageName?.substring(0, 1) ?: "#" + } - interface Listener { - fun onModelClicked(model: PackageModel) - } + interface Listener { + fun onModelClicked(model: PackageModel) + } - override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup, viewType: Int): SortedListAdapter.ViewHolder { - val rootView = inflater.inflate(R.layout.item_package, parent, false) - return PackageViewHolder(rootView, listener) - } + override fun onCreateViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + viewType: Int + ): SortedListAdapter.ViewHolder { + val rootView = inflater.inflate(R.layout.item_package, parent, false) + return PackageViewHolder(rootView, listener) + } } diff --git a/app/src/main/java/io/neoterm/ui/pm/adapter/holder/PackageViewHolder.kt b/app/src/main/java/io/neoterm/ui/pm/adapter/holder/PackageViewHolder.kt index f0dd7af..a17a130 100755 --- a/app/src/main/java/io/neoterm/ui/pm/adapter/holder/PackageViewHolder.kt +++ b/app/src/main/java/io/neoterm/ui/pm/adapter/holder/PackageViewHolder.kt @@ -9,13 +9,14 @@ import io.neoterm.R import io.neoterm.ui.pm.adapter.PackageAdapter import io.neoterm.ui.pm.model.PackageModel -class PackageViewHolder(private val rootView: View, private val listener: PackageAdapter.Listener) : SortedListAdapter.ViewHolder(rootView) { - private val packageNameView: TextView = rootView.findViewById(R.id.package_item_name) - private val packageDescView: TextView = rootView.findViewById(R.id.package_item_desc) +class PackageViewHolder(private val rootView: View, private val listener: PackageAdapter.Listener) : + SortedListAdapter.ViewHolder(rootView) { + private val packageNameView: TextView = rootView.findViewById(R.id.package_item_name) + private val packageDescView: TextView = rootView.findViewById(R.id.package_item_desc) - override fun performBind(item: PackageModel) { - rootView.setOnClickListener { listener.onModelClicked(item) } - packageNameView.text = item.packageInfo.packageName - packageDescView.text = item.packageInfo.description - } + override fun performBind(item: PackageModel) { + rootView.setOnClickListener { listener.onModelClicked(item) } + packageNameView.text = item.packageInfo.packageName + packageDescView.text = item.packageInfo.description + } } diff --git a/app/src/main/java/io/neoterm/ui/pm/model/PackageModel.kt b/app/src/main/java/io/neoterm/ui/pm/model/PackageModel.kt index 1fd1c3c..5fb6909 100644 --- a/app/src/main/java/io/neoterm/ui/pm/model/PackageModel.kt +++ b/app/src/main/java/io/neoterm/ui/pm/model/PackageModel.kt @@ -12,22 +12,24 @@ import io.neoterm.utils.FileUtils */ class PackageModel(val packageInfo: NeoPackageInfo) : SortedListAdapter.ViewModel { - override fun isSameModelAs(t: T): Boolean { - if (t is PackageModel) { - return t.packageInfo.packageName == packageInfo.packageName - } - return false + override fun isSameModelAs(t: T): Boolean { + if (t is PackageModel) { + return t.packageInfo.packageName == packageInfo.packageName } + return false + } - override fun isContentTheSameAs(t: T): Boolean { - return isSameModelAs(t) - } + override fun isContentTheSameAs(t: T): Boolean { + return isSameModelAs(t) + } - fun getPackageDetails(context: Context): String { - return context.getString(R.string.package_details, - packageInfo.packageName, packageInfo.version, - packageInfo.dependenciesString, - FileUtils.formatSizeInKB(packageInfo.installedSizeInBytes), - packageInfo.description, packageInfo.homePage) - } + fun getPackageDetails(context: Context): String { + return context.getString( + R.string.package_details, + packageInfo.packageName, packageInfo.version, + packageInfo.dependenciesString, + FileUtils.formatSizeInKB(packageInfo.installedSizeInBytes), + packageInfo.description, packageInfo.homePage + ) + } } diff --git a/app/src/main/java/io/neoterm/ui/pm/utils/StringDistance.java b/app/src/main/java/io/neoterm/ui/pm/utils/StringDistance.java index 14f9b20..27e9a95 100644 --- a/app/src/main/java/io/neoterm/ui/pm/utils/StringDistance.java +++ b/app/src/main/java/io/neoterm/ui/pm/utils/StringDistance.java @@ -5,34 +5,34 @@ package io.neoterm.ui.pm.utils; */ public class StringDistance { - public static int distance(String source, String target) { - char[] sources = source.toCharArray(); - char[] targets = target.toCharArray(); - int sourceLen = sources.length; - int targetLen = targets.length; + public static int distance(String source, String target) { + char[] sources = source.toCharArray(); + char[] targets = target.toCharArray(); + int sourceLen = sources.length; + int targetLen = targets.length; - int[][] d = new int[sourceLen + 1][targetLen + 1]; - for (int i = 0; i <= sourceLen; i++) { - d[i][0] = i; - } - for (int i = 0; i <= targetLen; i++) { - d[0][i] = i; - } - - for (int i = 1; i <= sourceLen; i++) { - for (int j = 1; j <= targetLen; j++) { - if (sources[i - 1] == targets[j - 1]) { - d[i][j] = d[i - 1][j - 1]; - } else { - int insert = d[i][j - 1] + 1; - int delete = d[i - 1][j] + 1; - int replace = d[i - 1][j - 1] + 1; - d[i][j] = Math.min(insert, delete) > Math.min(delete, replace) - ? Math.min(delete, replace) - : Math.min(insert, delete); - } - } - } - return d[sourceLen][targetLen]; + int[][] d = new int[sourceLen + 1][targetLen + 1]; + for (int i = 0; i <= sourceLen; i++) { + d[i][0] = i; } + for (int i = 0; i <= targetLen; i++) { + d[0][i] = i; + } + + for (int i = 1; i <= sourceLen; i++) { + for (int j = 1; j <= targetLen; j++) { + if (sources[i - 1] == targets[j - 1]) { + d[i][j] = d[i - 1][j - 1]; + } else { + int insert = d[i][j - 1] + 1; + int delete = d[i - 1][j] + 1; + int replace = d[i - 1][j - 1] + 1; + d[i][j] = Math.min(insert, delete) > Math.min(delete, replace) + ? Math.min(delete, replace) + : Math.min(insert, delete); + } + } + } + return d[sourceLen][targetLen]; + } } diff --git a/app/src/main/java/io/neoterm/ui/pm/view/RecyclerTabLayout.java b/app/src/main/java/io/neoterm/ui/pm/view/RecyclerTabLayout.java index 44d27c0..2b5bf23 100644 --- a/app/src/main/java/io/neoterm/ui/pm/view/RecyclerTabLayout.java +++ b/app/src/main/java/io/neoterm/ui/pm/view/RecyclerTabLayout.java @@ -22,605 +22,604 @@ import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; -import androidx.core.view.ViewCompat; -import androidx.viewpager.widget.ViewPager; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.appcompat.widget.AppCompatTextView; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - +import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.core.view.ViewCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager.widget.ViewPager; import io.neoterm.R; public class RecyclerTabLayout extends RecyclerView { - protected static final long DEFAULT_SCROLL_DURATION = 200; - protected static final float DEFAULT_POSITION_THRESHOLD = 0.6f; - protected static final float POSITION_THRESHOLD_ALLOWABLE = 0.001f; + protected static final long DEFAULT_SCROLL_DURATION = 200; + protected static final float DEFAULT_POSITION_THRESHOLD = 0.6f; + protected static final float POSITION_THRESHOLD_ALLOWABLE = 0.001f; + + protected Paint mIndicatorPaint; + protected int mTabBackgroundResId; + protected int mTabOnScreenLimit; + protected int mTabMinWidth; + protected int mTabMaxWidth; + protected int mTabTextAppearance; + protected int mTabSelectedTextColor; + protected boolean mTabSelectedTextColorSet; + protected int mTabPaddingStart; + protected int mTabPaddingTop; + protected int mTabPaddingEnd; + protected int mTabPaddingBottom; + protected int mIndicatorHeight; + + protected LinearLayoutManager mLinearLayoutManager; + protected RecyclerOnScrollListener mRecyclerOnScrollListener; + protected ViewPager mViewPager; + protected Adapter mAdapter; + + protected int mIndicatorPosition; + protected int mIndicatorGap; + protected int mIndicatorScroll; + private int mOldPosition; + private int mOldScrollOffset; + protected float mOldPositionOffset; + protected float mPositionThreshold; + protected boolean mRequestScrollToTab; + protected boolean mScrollEanbled; + + public RecyclerTabLayout(Context context) { + this(context, null); + } + + public RecyclerTabLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public RecyclerTabLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setWillNotDraw(false); + mIndicatorPaint = new Paint(); + getAttributes(context, attrs, defStyle); + mLinearLayoutManager = new LinearLayoutManager(getContext()) { + @Override + public boolean canScrollHorizontally() { + return mScrollEanbled; + } + }; + mLinearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); + setLayoutManager(mLinearLayoutManager); + setItemAnimator(null); + mPositionThreshold = DEFAULT_POSITION_THRESHOLD; + } + + private void getAttributes(Context context, AttributeSet attrs, int defStyle) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.rtl_RecyclerTabLayout, + defStyle, R.style.rtl_RecyclerTabLayout); + setIndicatorColor(a.getColor(R.styleable + .rtl_RecyclerTabLayout_rtl_tabIndicatorColor, 0)); + setIndicatorHeight(a.getDimensionPixelSize(R.styleable + .rtl_RecyclerTabLayout_rtl_tabIndicatorHeight, 0)); + + mTabTextAppearance = a.getResourceId(R.styleable.rtl_RecyclerTabLayout_rtl_tabTextAppearance, + R.style.rtl_RecyclerTabLayout_Tab); + + mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a + .getDimensionPixelSize(R.styleable.rtl_RecyclerTabLayout_rtl_tabPadding, 0); + mTabPaddingStart = a.getDimensionPixelSize( + R.styleable.rtl_RecyclerTabLayout_rtl_tabPaddingStart, mTabPaddingStart); + mTabPaddingTop = a.getDimensionPixelSize( + R.styleable.rtl_RecyclerTabLayout_rtl_tabPaddingTop, mTabPaddingTop); + mTabPaddingEnd = a.getDimensionPixelSize( + R.styleable.rtl_RecyclerTabLayout_rtl_tabPaddingEnd, mTabPaddingEnd); + mTabPaddingBottom = a.getDimensionPixelSize( + R.styleable.rtl_RecyclerTabLayout_rtl_tabPaddingBottom, mTabPaddingBottom); + + if (a.hasValue(R.styleable.rtl_RecyclerTabLayout_rtl_tabSelectedTextColor)) { + mTabSelectedTextColor = a + .getColor(R.styleable.rtl_RecyclerTabLayout_rtl_tabSelectedTextColor, 0); + mTabSelectedTextColorSet = true; + } + + mTabOnScreenLimit = a.getInteger( + R.styleable.rtl_RecyclerTabLayout_rtl_tabOnScreenLimit, 0); + if (mTabOnScreenLimit == 0) { + mTabMinWidth = a.getDimensionPixelSize( + R.styleable.rtl_RecyclerTabLayout_rtl_tabMinWidth, 0); + mTabMaxWidth = a.getDimensionPixelSize( + R.styleable.rtl_RecyclerTabLayout_rtl_tabMaxWidth, 0); + } + + mTabBackgroundResId = a + .getResourceId(R.styleable.rtl_RecyclerTabLayout_rtl_tabBackground, 0); + mScrollEanbled = a.getBoolean(R.styleable.rtl_RecyclerTabLayout_rtl_scrollEnabled, true); + a.recycle(); + } + + @Override + protected void onDetachedFromWindow() { + if (mRecyclerOnScrollListener != null) { + removeOnScrollListener(mRecyclerOnScrollListener); + mRecyclerOnScrollListener = null; + } + super.onDetachedFromWindow(); + } + + + public void setIndicatorColor(int color) { + mIndicatorPaint.setColor(color); + } + + public void setIndicatorHeight(int indicatorHeight) { + mIndicatorHeight = indicatorHeight; + } + + public void setAutoSelectionMode(boolean autoSelect) { + if (mRecyclerOnScrollListener != null) { + removeOnScrollListener(mRecyclerOnScrollListener); + mRecyclerOnScrollListener = null; + } + if (autoSelect) { + mRecyclerOnScrollListener = new RecyclerOnScrollListener(this, mLinearLayoutManager); + addOnScrollListener(mRecyclerOnScrollListener); + } + } + + public void setPositionThreshold(float positionThreshold) { + mPositionThreshold = positionThreshold; + } + + public void setUpWithViewPager(ViewPager viewPager) { + DefaultAdapter adapter = new DefaultAdapter(viewPager); + adapter.setTabPadding(mTabPaddingStart, mTabPaddingTop, mTabPaddingEnd, mTabPaddingBottom); + adapter.setTabTextAppearance(mTabTextAppearance); + adapter.setTabSelectedTextColor(mTabSelectedTextColorSet, mTabSelectedTextColor); + adapter.setTabMaxWidth(mTabMaxWidth); + adapter.setTabMinWidth(mTabMinWidth); + adapter.setTabBackgroundResId(mTabBackgroundResId); + adapter.setTabOnScreenLimit(mTabOnScreenLimit); + setUpWithAdapter(adapter); + } + + public void setUpWithAdapter(RecyclerTabLayout.Adapter adapter) { + mAdapter = adapter; + mViewPager = adapter.getViewPager(); + if (mViewPager.getAdapter() == null) { + throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set"); + } + mViewPager.addOnPageChangeListener(new ViewPagerOnPageChangeListener(this)); + setAdapter(adapter); + scrollToTab(mViewPager.getCurrentItem()); + } + + public void setCurrentItem(int position, boolean smoothScroll) { + if (mViewPager != null) { + mViewPager.setCurrentItem(position, smoothScroll); + scrollToTab(mViewPager.getCurrentItem()); + return; + } + + if (smoothScroll && position != mIndicatorPosition) { + startAnimation(position); + + } else { + scrollToTab(position); + } + } + + protected void startAnimation(final int position) { + + float distance = 1; + + View view = mLinearLayoutManager.findViewByPosition(position); + if (view != null) { + float currentX = view.getX() + view.getMeasuredWidth() / 2.f; + float centerX = getMeasuredWidth() / 2.f; + distance = Math.abs(centerX - currentX) / view.getMeasuredWidth(); + } + + ValueAnimator animator; + if (position < mIndicatorPosition) { + animator = ValueAnimator.ofFloat(distance, 0); + } else { + animator = ValueAnimator.ofFloat(-distance, 0); + } + animator.setDuration(DEFAULT_SCROLL_DURATION); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + scrollToTab(position, (float) animation.getAnimatedValue(), true); + } + }); + animator.start(); + } + + protected void scrollToTab(int position) { + scrollToTab(position, 0, false); + mAdapter.setCurrentIndicatorPosition(position); + mAdapter.notifyDataSetChanged(); + } + + protected void scrollToTab(int position, float positionOffset, boolean fitIndicator) { + int scrollOffset = 0; + + View selectedView = mLinearLayoutManager.findViewByPosition(position); + View nextView = mLinearLayoutManager.findViewByPosition(position + 1); + + if (selectedView != null) { + int width = getMeasuredWidth(); + float sLeft = (position == 0) ? 0 : width / 2.f - selectedView.getMeasuredWidth() / 2.f; // left edge of selected tab + float sRight = sLeft + selectedView.getMeasuredWidth(); // right edge of selected tab + + if (nextView != null) { + float nLeft = width / 2.f - nextView.getMeasuredWidth() / 2.f; // left edge of next tab + float distance = sRight - nLeft; // total distance that is needed to distance to next tab + float dx = distance * positionOffset; + scrollOffset = (int) (sLeft - dx); + + if (position == 0) { + float indicatorGap = (nextView.getMeasuredWidth() - selectedView.getMeasuredWidth()) / 2; + mIndicatorGap = (int) (indicatorGap * positionOffset); + mIndicatorScroll = (int) ((selectedView.getMeasuredWidth() + indicatorGap) * positionOffset); + + } else { + float indicatorGap = (nextView.getMeasuredWidth() - selectedView.getMeasuredWidth()) / 2; + mIndicatorGap = (int) (indicatorGap * positionOffset); + mIndicatorScroll = (int) dx; + } + + } else { + scrollOffset = (int) sLeft; + mIndicatorScroll = 0; + mIndicatorGap = 0; + } + if (fitIndicator) { + mIndicatorScroll = 0; + mIndicatorGap = 0; + } + + } else { + if (getMeasuredWidth() > 0 && mTabMaxWidth > 0 && mTabMinWidth == mTabMaxWidth) { //fixed size + int width = mTabMinWidth; + int offset = (int) (positionOffset * -width); + int leftOffset = (int) ((getMeasuredWidth() - width) / 2.f); + scrollOffset = offset + leftOffset; + } + mRequestScrollToTab = true; + } + + updateCurrentIndicatorPosition(position, positionOffset - mOldPositionOffset, positionOffset); + mIndicatorPosition = position; + + stopScroll(); + + if (position != mOldPosition || scrollOffset != mOldScrollOffset) { + mLinearLayoutManager.scrollToPositionWithOffset(position, scrollOffset); + } + if (mIndicatorHeight > 0) { + invalidate(); + } + + mOldPosition = position; + mOldScrollOffset = scrollOffset; + mOldPositionOffset = positionOffset; + } + + protected void updateCurrentIndicatorPosition(int position, float dx, float positionOffset) { + if (mAdapter == null) { + return; + } + int indicatorPosition = -1; + if (dx > 0 && positionOffset >= mPositionThreshold - POSITION_THRESHOLD_ALLOWABLE) { + indicatorPosition = position + 1; + + } else if (dx < 0 && positionOffset <= 1 - mPositionThreshold + POSITION_THRESHOLD_ALLOWABLE) { + indicatorPosition = position; + } + if (indicatorPosition >= 0 && indicatorPosition != mAdapter.getCurrentIndicatorPosition()) { + mAdapter.setCurrentIndicatorPosition(indicatorPosition); + mAdapter.notifyDataSetChanged(); + } + } + + @Override + public void onDraw(Canvas canvas) { + View view = mLinearLayoutManager.findViewByPosition(mIndicatorPosition); + if (view == null) { + if (mRequestScrollToTab) { + mRequestScrollToTab = false; + scrollToTab(mViewPager.getCurrentItem()); + } + return; + } + mRequestScrollToTab = false; + + int left; + int right; + if (isLayoutRtl()) { + left = view.getLeft() - mIndicatorScroll - mIndicatorGap; + right = view.getRight() - mIndicatorScroll + mIndicatorGap; + } else { + left = view.getLeft() + mIndicatorScroll - mIndicatorGap; + right = view.getRight() + mIndicatorScroll + mIndicatorGap; + } + + int top = getHeight() - mIndicatorHeight; + int bottom = getHeight(); + + canvas.drawRect(left, top, right, bottom, mIndicatorPaint); + } + + protected boolean isLayoutRtl() { + return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL; + } + + protected static class RecyclerOnScrollListener extends OnScrollListener { + + protected RecyclerTabLayout mRecyclerTabLayout; + protected LinearLayoutManager mLinearLayoutManager; + + public RecyclerOnScrollListener(RecyclerTabLayout recyclerTabLayout, + LinearLayoutManager linearLayoutManager) { + mRecyclerTabLayout = recyclerTabLayout; + mLinearLayoutManager = linearLayoutManager; + } + + public int mDx; + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + mDx += dx; + } + + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + switch (newState) { + case SCROLL_STATE_IDLE: + if (mDx > 0) { + selectCenterTabForRightScroll(); + } else { + selectCenterTabForLeftScroll(); + } + mDx = 0; + break; + case SCROLL_STATE_DRAGGING: + case SCROLL_STATE_SETTLING: + } + } + + protected void selectCenterTabForRightScroll() { + int first = mLinearLayoutManager.findFirstVisibleItemPosition(); + int last = mLinearLayoutManager.findLastVisibleItemPosition(); + int center = mRecyclerTabLayout.getWidth() / 2; + for (int position = first; position <= last; position++) { + View view = mLinearLayoutManager.findViewByPosition(position); + if (view.getLeft() + view.getWidth() >= center) { + mRecyclerTabLayout.setCurrentItem(position, false); + break; + } + } + } + + protected void selectCenterTabForLeftScroll() { + int first = mLinearLayoutManager.findFirstVisibleItemPosition(); + int last = mLinearLayoutManager.findLastVisibleItemPosition(); + int center = mRecyclerTabLayout.getWidth() / 2; + for (int position = last; position >= first; position--) { + View view = mLinearLayoutManager.findViewByPosition(position); + if (view.getLeft() <= center) { + mRecyclerTabLayout.setCurrentItem(position, false); + break; + } + } + } + } + + protected static class ViewPagerOnPageChangeListener implements ViewPager.OnPageChangeListener { + + private final RecyclerTabLayout mRecyclerTabLayout; + private int mScrollState; + + public ViewPagerOnPageChangeListener(RecyclerTabLayout recyclerTabLayout) { + mRecyclerTabLayout = recyclerTabLayout; + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + mRecyclerTabLayout.scrollToTab(position, positionOffset, false); + } + + @Override + public void onPageScrollStateChanged(int state) { + mScrollState = state; + } + + @Override + public void onPageSelected(int position) { + if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { + if (mRecyclerTabLayout.mIndicatorPosition != position) { + mRecyclerTabLayout.scrollToTab(position); + } + } + } + } + + public static abstract class Adapter + extends RecyclerView.Adapter { + + protected ViewPager mViewPager; + protected int mIndicatorPosition; + + public Adapter(ViewPager viewPager) { + mViewPager = viewPager; + } + + public ViewPager getViewPager() { + return mViewPager; + } + + public void setCurrentIndicatorPosition(int indicatorPosition) { + mIndicatorPosition = indicatorPosition; + } + + public int getCurrentIndicatorPosition() { + return mIndicatorPosition; + } + } + + public static class DefaultAdapter + extends RecyclerTabLayout.Adapter { + + protected static final int MAX_TAB_TEXT_LINES = 2; - protected Paint mIndicatorPaint; - protected int mTabBackgroundResId; - protected int mTabOnScreenLimit; - protected int mTabMinWidth; - protected int mTabMaxWidth; - protected int mTabTextAppearance; - protected int mTabSelectedTextColor; - protected boolean mTabSelectedTextColorSet; protected int mTabPaddingStart; protected int mTabPaddingTop; protected int mTabPaddingEnd; protected int mTabPaddingBottom; - protected int mIndicatorHeight; + protected int mTabTextAppearance; + protected boolean mTabSelectedTextColorSet; + protected int mTabSelectedTextColor; + private int mTabMaxWidth; + private int mTabMinWidth; + private int mTabBackgroundResId; + private int mTabOnScreenLimit; - protected LinearLayoutManager mLinearLayoutManager; - protected RecyclerOnScrollListener mRecyclerOnScrollListener; - protected ViewPager mViewPager; - protected Adapter mAdapter; - - protected int mIndicatorPosition; - protected int mIndicatorGap; - protected int mIndicatorScroll; - private int mOldPosition; - private int mOldScrollOffset; - protected float mOldPositionOffset; - protected float mPositionThreshold; - protected boolean mRequestScrollToTab; - protected boolean mScrollEanbled; - - public RecyclerTabLayout(Context context) { - this(context, null); + public DefaultAdapter(ViewPager viewPager) { + super(viewPager); } - public RecyclerTabLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } + @SuppressWarnings("deprecation") + @Override + public DefaultAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + TabTextView tabTextView = new TabTextView(parent.getContext()); - public RecyclerTabLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setWillNotDraw(false); - mIndicatorPaint = new Paint(); - getAttributes(context, attrs, defStyle); - mLinearLayoutManager = new LinearLayoutManager(getContext()) { - @Override - public boolean canScrollHorizontally() { - return mScrollEanbled; - } - }; - mLinearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); - setLayoutManager(mLinearLayoutManager); - setItemAnimator(null); - mPositionThreshold = DEFAULT_POSITION_THRESHOLD; - } + if (mTabSelectedTextColorSet) { + tabTextView.setTextColor(tabTextView.createColorStateList( + tabTextView.getCurrentTextColor(), mTabSelectedTextColor)); + } - private void getAttributes(Context context, AttributeSet attrs, int defStyle) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.rtl_RecyclerTabLayout, - defStyle, R.style.rtl_RecyclerTabLayout); - setIndicatorColor(a.getColor(R.styleable - .rtl_RecyclerTabLayout_rtl_tabIndicatorColor, 0)); - setIndicatorHeight(a.getDimensionPixelSize(R.styleable - .rtl_RecyclerTabLayout_rtl_tabIndicatorHeight, 0)); + ViewCompat.setPaddingRelative(tabTextView, mTabPaddingStart, mTabPaddingTop, + mTabPaddingEnd, mTabPaddingBottom); + tabTextView.setTextAppearance(parent.getContext(), mTabTextAppearance); + tabTextView.setGravity(Gravity.CENTER); + tabTextView.setMaxLines(MAX_TAB_TEXT_LINES); + tabTextView.setEllipsize(TextUtils.TruncateAt.END); - mTabTextAppearance = a.getResourceId(R.styleable.rtl_RecyclerTabLayout_rtl_tabTextAppearance, - R.style.rtl_RecyclerTabLayout_Tab); + if (mTabOnScreenLimit > 0) { + int width = parent.getMeasuredWidth() / mTabOnScreenLimit; + tabTextView.setMaxWidth(width); + tabTextView.setMinWidth(width); - mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a - .getDimensionPixelSize(R.styleable.rtl_RecyclerTabLayout_rtl_tabPadding, 0); - mTabPaddingStart = a.getDimensionPixelSize( - R.styleable.rtl_RecyclerTabLayout_rtl_tabPaddingStart, mTabPaddingStart); - mTabPaddingTop = a.getDimensionPixelSize( - R.styleable.rtl_RecyclerTabLayout_rtl_tabPaddingTop, mTabPaddingTop); - mTabPaddingEnd = a.getDimensionPixelSize( - R.styleable.rtl_RecyclerTabLayout_rtl_tabPaddingEnd, mTabPaddingEnd); - mTabPaddingBottom = a.getDimensionPixelSize( - R.styleable.rtl_RecyclerTabLayout_rtl_tabPaddingBottom, mTabPaddingBottom); - - if (a.hasValue(R.styleable.rtl_RecyclerTabLayout_rtl_tabSelectedTextColor)) { - mTabSelectedTextColor = a - .getColor(R.styleable.rtl_RecyclerTabLayout_rtl_tabSelectedTextColor, 0); - mTabSelectedTextColorSet = true; + } else { + if (mTabMaxWidth > 0) { + tabTextView.setMaxWidth(mTabMaxWidth); } + tabTextView.setMinWidth(mTabMinWidth); + } - mTabOnScreenLimit = a.getInteger( - R.styleable.rtl_RecyclerTabLayout_rtl_tabOnScreenLimit, 0); - if (mTabOnScreenLimit == 0) { - mTabMinWidth = a.getDimensionPixelSize( - R.styleable.rtl_RecyclerTabLayout_rtl_tabMinWidth, 0); - mTabMaxWidth = a.getDimensionPixelSize( - R.styleable.rtl_RecyclerTabLayout_rtl_tabMaxWidth, 0); - } - - mTabBackgroundResId = a - .getResourceId(R.styleable.rtl_RecyclerTabLayout_rtl_tabBackground, 0); - mScrollEanbled = a.getBoolean(R.styleable.rtl_RecyclerTabLayout_rtl_scrollEnabled, true); - a.recycle(); + tabTextView.setTextAppearance(tabTextView.getContext(), mTabTextAppearance); + if (mTabSelectedTextColorSet) { + tabTextView.setTextColor(tabTextView.createColorStateList( + tabTextView.getCurrentTextColor(), mTabSelectedTextColor)); + } + if (mTabBackgroundResId != 0) { + tabTextView.setBackgroundDrawable( + AppCompatResources.getDrawable(tabTextView.getContext(), mTabBackgroundResId)); + } + tabTextView.setLayoutParams(createLayoutParamsForTabs()); + return new ViewHolder(tabTextView); } @Override - protected void onDetachedFromWindow() { - if (mRecyclerOnScrollListener != null) { - removeOnScrollListener(mRecyclerOnScrollListener); - mRecyclerOnScrollListener = null; - } - super.onDetachedFromWindow(); + public void onBindViewHolder(DefaultAdapter.ViewHolder holder, int position) { + CharSequence title = getViewPager().getAdapter().getPageTitle(position); + holder.title.setText(title); + holder.title.setSelected(getCurrentIndicatorPosition() == position); } - - public void setIndicatorColor(int color) { - mIndicatorPaint.setColor(color); + @Override + public int getItemCount() { + return getViewPager().getAdapter().getCount(); } - public void setIndicatorHeight(int indicatorHeight) { - mIndicatorHeight = indicatorHeight; + public void setTabPadding(int tabPaddingStart, int tabPaddingTop, int tabPaddingEnd, + int tabPaddingBottom) { + mTabPaddingStart = tabPaddingStart; + mTabPaddingTop = tabPaddingTop; + mTabPaddingEnd = tabPaddingEnd; + mTabPaddingBottom = tabPaddingBottom; } - public void setAutoSelectionMode(boolean autoSelect) { - if (mRecyclerOnScrollListener != null) { - removeOnScrollListener(mRecyclerOnScrollListener); - mRecyclerOnScrollListener = null; - } - if (autoSelect) { - mRecyclerOnScrollListener = new RecyclerOnScrollListener(this, mLinearLayoutManager); - addOnScrollListener(mRecyclerOnScrollListener); - } + public void setTabTextAppearance(int tabTextAppearance) { + mTabTextAppearance = tabTextAppearance; } - public void setPositionThreshold(float positionThreshold) { - mPositionThreshold = positionThreshold; + public void setTabSelectedTextColor(boolean tabSelectedTextColorSet, + int tabSelectedTextColor) { + mTabSelectedTextColorSet = tabSelectedTextColorSet; + mTabSelectedTextColor = tabSelectedTextColor; } - public void setUpWithViewPager(ViewPager viewPager) { - DefaultAdapter adapter = new DefaultAdapter(viewPager); - adapter.setTabPadding(mTabPaddingStart, mTabPaddingTop, mTabPaddingEnd, mTabPaddingBottom); - adapter.setTabTextAppearance(mTabTextAppearance); - adapter.setTabSelectedTextColor(mTabSelectedTextColorSet, mTabSelectedTextColor); - adapter.setTabMaxWidth(mTabMaxWidth); - adapter.setTabMinWidth(mTabMinWidth); - adapter.setTabBackgroundResId(mTabBackgroundResId); - adapter.setTabOnScreenLimit(mTabOnScreenLimit); - setUpWithAdapter(adapter); + public void setTabMaxWidth(int tabMaxWidth) { + mTabMaxWidth = tabMaxWidth; } - public void setUpWithAdapter(RecyclerTabLayout.Adapter adapter) { - mAdapter = adapter; - mViewPager = adapter.getViewPager(); - if (mViewPager.getAdapter() == null) { - throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set"); - } - mViewPager.addOnPageChangeListener(new ViewPagerOnPageChangeListener(this)); - setAdapter(adapter); - scrollToTab(mViewPager.getCurrentItem()); + public void setTabMinWidth(int tabMinWidth) { + mTabMinWidth = tabMinWidth; } - public void setCurrentItem(int position, boolean smoothScroll) { - if (mViewPager != null) { - mViewPager.setCurrentItem(position, smoothScroll); - scrollToTab(mViewPager.getCurrentItem()); - return; - } - - if (smoothScroll && position != mIndicatorPosition) { - startAnimation(position); - - } else { - scrollToTab(position); - } + public void setTabBackgroundResId(int tabBackgroundResId) { + mTabBackgroundResId = tabBackgroundResId; } - protected void startAnimation(final int position) { + public void setTabOnScreenLimit(int tabOnScreenLimit) { + mTabOnScreenLimit = tabOnScreenLimit; + } - float distance = 1; + protected RecyclerView.LayoutParams createLayoutParamsForTabs() { + return new RecyclerView.LayoutParams( + LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); + } - View view = mLinearLayoutManager.findViewByPosition(position); - if (view != null) { - float currentX = view.getX() + view.getMeasuredWidth() / 2.f; - float centerX = getMeasuredWidth() / 2.f; - distance = Math.abs(centerX - currentX) / view.getMeasuredWidth(); - } + public class ViewHolder extends RecyclerView.ViewHolder { - ValueAnimator animator; - if (position < mIndicatorPosition) { - animator = ValueAnimator.ofFloat(distance, 0); - } else { - animator = ValueAnimator.ofFloat(-distance, 0); - } - animator.setDuration(DEFAULT_SCROLL_DURATION); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - scrollToTab(position, (float) animation.getAnimatedValue(), true); + public TextView title; + + public ViewHolder(View itemView) { + super(itemView); + title = (TextView) itemView; + itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int pos = getAdapterPosition(); + if (pos != NO_POSITION) { + getViewPager().setCurrentItem(pos, true); } + } }); - animator.start(); + } + } + } + + + public static class TabTextView extends AppCompatTextView { + + public TabTextView(Context context) { + super(context); } - protected void scrollToTab(int position) { - scrollToTab(position, 0, false); - mAdapter.setCurrentIndicatorPosition(position); - mAdapter.notifyDataSetChanged(); - } - - protected void scrollToTab(int position, float positionOffset, boolean fitIndicator) { - int scrollOffset = 0; - - View selectedView = mLinearLayoutManager.findViewByPosition(position); - View nextView = mLinearLayoutManager.findViewByPosition(position + 1); - - if (selectedView != null) { - int width = getMeasuredWidth(); - float sLeft = (position == 0) ? 0 : width / 2.f - selectedView.getMeasuredWidth() / 2.f; // left edge of selected tab - float sRight = sLeft + selectedView.getMeasuredWidth(); // right edge of selected tab - - if (nextView != null) { - float nLeft = width / 2.f - nextView.getMeasuredWidth() / 2.f; // left edge of next tab - float distance = sRight - nLeft; // total distance that is needed to distance to next tab - float dx = distance * positionOffset; - scrollOffset = (int) (sLeft - dx); - - if (position == 0) { - float indicatorGap = (nextView.getMeasuredWidth() - selectedView.getMeasuredWidth()) / 2; - mIndicatorGap = (int) (indicatorGap * positionOffset); - mIndicatorScroll = (int)((selectedView.getMeasuredWidth() + indicatorGap) * positionOffset); - - } else { - float indicatorGap = (nextView.getMeasuredWidth() - selectedView.getMeasuredWidth()) / 2; - mIndicatorGap = (int) (indicatorGap * positionOffset); - mIndicatorScroll = (int) dx; - } - - } else { - scrollOffset = (int) sLeft; - mIndicatorScroll = 0; - mIndicatorGap = 0; - } - if (fitIndicator) { - mIndicatorScroll = 0; - mIndicatorGap = 0; - } - - } else { - if (getMeasuredWidth() > 0 && mTabMaxWidth > 0 && mTabMinWidth == mTabMaxWidth) { //fixed size - int width = mTabMinWidth; - int offset = (int) (positionOffset * -width); - int leftOffset = (int) ((getMeasuredWidth() - width) / 2.f); - scrollOffset = offset + leftOffset; - } - mRequestScrollToTab = true; - } - - updateCurrentIndicatorPosition(position, positionOffset - mOldPositionOffset, positionOffset); - mIndicatorPosition = position; - - stopScroll(); - - if (position != mOldPosition || scrollOffset != mOldScrollOffset) { - mLinearLayoutManager.scrollToPositionWithOffset(position, scrollOffset); - } - if (mIndicatorHeight > 0) { - invalidate(); - } - - mOldPosition = position; - mOldScrollOffset = scrollOffset; - mOldPositionOffset = positionOffset; - } - - protected void updateCurrentIndicatorPosition(int position, float dx, float positionOffset) { - if (mAdapter == null) { - return; - } - int indicatorPosition = -1; - if (dx > 0 && positionOffset >= mPositionThreshold - POSITION_THRESHOLD_ALLOWABLE) { - indicatorPosition = position + 1; - - } else if (dx < 0 && positionOffset <= 1 - mPositionThreshold + POSITION_THRESHOLD_ALLOWABLE) { - indicatorPosition = position; - } - if (indicatorPosition >= 0 && indicatorPosition != mAdapter.getCurrentIndicatorPosition()) { - mAdapter.setCurrentIndicatorPosition(indicatorPosition); - mAdapter.notifyDataSetChanged(); - } - } - - @Override - public void onDraw(Canvas canvas) { - View view = mLinearLayoutManager.findViewByPosition(mIndicatorPosition); - if (view == null) { - if (mRequestScrollToTab) { - mRequestScrollToTab = false; - scrollToTab(mViewPager.getCurrentItem()); - } - return; - } - mRequestScrollToTab = false; - - int left; - int right; - if (isLayoutRtl()) { - left = view.getLeft() - mIndicatorScroll - mIndicatorGap; - right = view.getRight() - mIndicatorScroll + mIndicatorGap; - } else { - left = view.getLeft() + mIndicatorScroll - mIndicatorGap; - right = view.getRight() + mIndicatorScroll + mIndicatorGap; - } - - int top = getHeight() - mIndicatorHeight; - int bottom = getHeight(); - - canvas.drawRect(left, top, right, bottom, mIndicatorPaint); - } - - protected boolean isLayoutRtl() { - return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL; - } - - protected static class RecyclerOnScrollListener extends OnScrollListener { - - protected RecyclerTabLayout mRecyclerTabLayout; - protected LinearLayoutManager mLinearLayoutManager; - - public RecyclerOnScrollListener(RecyclerTabLayout recyclerTabLayout, - LinearLayoutManager linearLayoutManager) { - mRecyclerTabLayout = recyclerTabLayout; - mLinearLayoutManager = linearLayoutManager; - } - - public int mDx; - - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - mDx += dx; - } - - @Override - public void onScrollStateChanged(RecyclerView recyclerView, int newState) { - switch (newState) { - case SCROLL_STATE_IDLE: - if (mDx > 0) { - selectCenterTabForRightScroll(); - } else { - selectCenterTabForLeftScroll(); - } - mDx = 0; - break; - case SCROLL_STATE_DRAGGING: - case SCROLL_STATE_SETTLING: - } - } - - protected void selectCenterTabForRightScroll() { - int first = mLinearLayoutManager.findFirstVisibleItemPosition(); - int last = mLinearLayoutManager.findLastVisibleItemPosition(); - int center = mRecyclerTabLayout.getWidth() / 2; - for (int position = first; position <= last; position++) { - View view = mLinearLayoutManager.findViewByPosition(position); - if (view.getLeft() + view.getWidth() >= center) { - mRecyclerTabLayout.setCurrentItem(position, false); - break; - } - } - } - - protected void selectCenterTabForLeftScroll() { - int first = mLinearLayoutManager.findFirstVisibleItemPosition(); - int last = mLinearLayoutManager.findLastVisibleItemPosition(); - int center = mRecyclerTabLayout.getWidth() / 2; - for (int position = last; position >= first; position--) { - View view = mLinearLayoutManager.findViewByPosition(position); - if (view.getLeft() <= center) { - mRecyclerTabLayout.setCurrentItem(position, false); - break; - } - } - } - } - - protected static class ViewPagerOnPageChangeListener implements ViewPager.OnPageChangeListener { - - private final RecyclerTabLayout mRecyclerTabLayout; - private int mScrollState; - - public ViewPagerOnPageChangeListener(RecyclerTabLayout recyclerTabLayout) { - mRecyclerTabLayout = recyclerTabLayout; - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - mRecyclerTabLayout.scrollToTab(position, positionOffset, false); - } - - @Override - public void onPageScrollStateChanged(int state) { - mScrollState = state; - } - - @Override - public void onPageSelected(int position) { - if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { - if (mRecyclerTabLayout.mIndicatorPosition != position) { - mRecyclerTabLayout.scrollToTab(position); - } - } - } - } - - public static abstract class Adapter - extends RecyclerView.Adapter { - - protected ViewPager mViewPager; - protected int mIndicatorPosition; - - public Adapter(ViewPager viewPager) { - mViewPager = viewPager; - } - - public ViewPager getViewPager() { - return mViewPager; - } - - public void setCurrentIndicatorPosition(int indicatorPosition) { - mIndicatorPosition = indicatorPosition; - } - - public int getCurrentIndicatorPosition() { - return mIndicatorPosition; - } - } - - public static class DefaultAdapter - extends RecyclerTabLayout.Adapter { - - protected static final int MAX_TAB_TEXT_LINES = 2; - - protected int mTabPaddingStart; - protected int mTabPaddingTop; - protected int mTabPaddingEnd; - protected int mTabPaddingBottom; - protected int mTabTextAppearance; - protected boolean mTabSelectedTextColorSet; - protected int mTabSelectedTextColor; - private int mTabMaxWidth; - private int mTabMinWidth; - private int mTabBackgroundResId; - private int mTabOnScreenLimit; - - public DefaultAdapter(ViewPager viewPager) { - super(viewPager); - } - - @SuppressWarnings("deprecation") - @Override - public DefaultAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - TabTextView tabTextView = new TabTextView(parent.getContext()); - - if (mTabSelectedTextColorSet) { - tabTextView.setTextColor(tabTextView.createColorStateList( - tabTextView.getCurrentTextColor(), mTabSelectedTextColor)); - } - - ViewCompat.setPaddingRelative(tabTextView, mTabPaddingStart, mTabPaddingTop, - mTabPaddingEnd, mTabPaddingBottom); - tabTextView.setTextAppearance(parent.getContext(), mTabTextAppearance); - tabTextView.setGravity(Gravity.CENTER); - tabTextView.setMaxLines(MAX_TAB_TEXT_LINES); - tabTextView.setEllipsize(TextUtils.TruncateAt.END); - - if (mTabOnScreenLimit > 0) { - int width = parent.getMeasuredWidth() / mTabOnScreenLimit; - tabTextView.setMaxWidth(width); - tabTextView.setMinWidth(width); - - } else { - if (mTabMaxWidth > 0) { - tabTextView.setMaxWidth(mTabMaxWidth); - } - tabTextView.setMinWidth(mTabMinWidth); - } - - tabTextView.setTextAppearance(tabTextView.getContext(), mTabTextAppearance); - if (mTabSelectedTextColorSet) { - tabTextView.setTextColor(tabTextView.createColorStateList( - tabTextView.getCurrentTextColor(), mTabSelectedTextColor)); - } - if (mTabBackgroundResId != 0) { - tabTextView.setBackgroundDrawable( - AppCompatResources.getDrawable(tabTextView.getContext(), mTabBackgroundResId)); - } - tabTextView.setLayoutParams(createLayoutParamsForTabs()); - return new ViewHolder(tabTextView); - } - - @Override - public void onBindViewHolder(DefaultAdapter.ViewHolder holder, int position) { - CharSequence title = getViewPager().getAdapter().getPageTitle(position); - holder.title.setText(title); - holder.title.setSelected(getCurrentIndicatorPosition() == position); - } - - @Override - public int getItemCount() { - return getViewPager().getAdapter().getCount(); - } - - public void setTabPadding(int tabPaddingStart, int tabPaddingTop, int tabPaddingEnd, - int tabPaddingBottom) { - mTabPaddingStart = tabPaddingStart; - mTabPaddingTop = tabPaddingTop; - mTabPaddingEnd = tabPaddingEnd; - mTabPaddingBottom = tabPaddingBottom; - } - - public void setTabTextAppearance(int tabTextAppearance) { - mTabTextAppearance = tabTextAppearance; - } - - public void setTabSelectedTextColor(boolean tabSelectedTextColorSet, - int tabSelectedTextColor) { - mTabSelectedTextColorSet = tabSelectedTextColorSet; - mTabSelectedTextColor = tabSelectedTextColor; - } - - public void setTabMaxWidth(int tabMaxWidth) { - mTabMaxWidth = tabMaxWidth; - } - - public void setTabMinWidth(int tabMinWidth) { - mTabMinWidth = tabMinWidth; - } - - public void setTabBackgroundResId(int tabBackgroundResId) { - mTabBackgroundResId = tabBackgroundResId; - } - - public void setTabOnScreenLimit(int tabOnScreenLimit) { - mTabOnScreenLimit = tabOnScreenLimit; - } - - protected RecyclerView.LayoutParams createLayoutParamsForTabs() { - return new RecyclerView.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); - } - - public class ViewHolder extends RecyclerView.ViewHolder { - - public TextView title; - - public ViewHolder(View itemView) { - super(itemView); - title = (TextView) itemView; - itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - int pos = getAdapterPosition(); - if (pos != NO_POSITION) { - getViewPager().setCurrentItem(pos, true); - } - } - }); - } - } - } - - - public static class TabTextView extends AppCompatTextView { - - public TabTextView(Context context) { - super(context); - } - - public ColorStateList createColorStateList(int defaultColor, int selectedColor) { - final int[][] states = new int[2][]; - final int[] colors = new int[2]; - states[0] = SELECTED_STATE_SET; - colors[0] = selectedColor; - // Default enabled state - states[1] = EMPTY_STATE_SET; - colors[1] = defaultColor; - return new ColorStateList(states, colors); - } + public ColorStateList createColorStateList(int defaultColor, int selectedColor) { + final int[][] states = new int[2][]; + final int[] colors = new int[2]; + states[0] = SELECTED_STATE_SET; + colors[0] = selectedColor; + // Default enabled state + states[1] = EMPTY_STATE_SET; + colors[1] = defaultColor; + return new ColorStateList(states, colors); } + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/settings/BasePreferenceActivity.kt b/app/src/main/java/io/neoterm/ui/settings/BasePreferenceActivity.kt index a245c88..c6ca738 100644 --- a/app/src/main/java/io/neoterm/ui/settings/BasePreferenceActivity.kt +++ b/app/src/main/java/io/neoterm/ui/settings/BasePreferenceActivity.kt @@ -18,12 +18,12 @@ package io.neoterm.ui.settings import android.content.res.Configuration import android.os.Bundle import android.preference.PreferenceActivity -import androidx.annotation.LayoutRes -import androidx.appcompat.app.ActionBar -import androidx.appcompat.app.AppCompatDelegate import android.view.MenuInflater import android.view.View import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.appcompat.app.ActionBar +import androidx.appcompat.app.AppCompatDelegate /** * A [android.preference.PreferenceActivity] which implements and proxies the necessary calls @@ -34,76 +34,76 @@ import android.view.ViewGroup * [android.preference.PreferenceActivity]. */ abstract class BasePreferenceActivity : PreferenceActivity() { - private var mDelegate: AppCompatDelegate? = null + private var mDelegate: AppCompatDelegate? = null - override fun onCreate(savedInstanceState: Bundle?) { - delegate.installViewFactory() - delegate.onCreate(savedInstanceState) - super.onCreate(savedInstanceState) + override fun onCreate(savedInstanceState: Bundle?) { + delegate.installViewFactory() + delegate.onCreate(savedInstanceState) + super.onCreate(savedInstanceState) + } + + override fun onPostCreate(savedInstanceState: Bundle?) { + super.onPostCreate(savedInstanceState) + delegate.onPostCreate(savedInstanceState) + } + + val supportActionBar: ActionBar? + get() = delegate.supportActionBar + + override fun getMenuInflater(): MenuInflater { + return delegate.menuInflater + } + + override fun setContentView(@LayoutRes layoutResID: Int) { + delegate.setContentView(layoutResID) + } + + override fun setContentView(view: View) { + delegate.setContentView(view) + } + + override fun setContentView(view: View, params: ViewGroup.LayoutParams) { + delegate.setContentView(view, params) + } + + override fun addContentView(view: View, params: ViewGroup.LayoutParams) { + delegate.addContentView(view, params) + } + + override fun onPostResume() { + super.onPostResume() + delegate.onPostResume() + } + + override fun onTitleChanged(title: CharSequence, color: Int) { + super.onTitleChanged(title, color) + delegate.setTitle(title) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + delegate.onConfigurationChanged(newConfig) + } + + override fun onStop() { + super.onStop() + delegate.onStop() + } + + override fun onDestroy() { + super.onDestroy() + delegate.onDestroy() + } + + override fun invalidateOptionsMenu() { + delegate.invalidateOptionsMenu() + } + + private val delegate: AppCompatDelegate + get() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null) + } + return mDelegate!! } - - override fun onPostCreate(savedInstanceState: Bundle?) { - super.onPostCreate(savedInstanceState) - delegate.onPostCreate(savedInstanceState) - } - - val supportActionBar: ActionBar? - get() = delegate.supportActionBar - - override fun getMenuInflater(): MenuInflater { - return delegate.menuInflater - } - - override fun setContentView(@LayoutRes layoutResID: Int) { - delegate.setContentView(layoutResID) - } - - override fun setContentView(view: View) { - delegate.setContentView(view) - } - - override fun setContentView(view: View, params: ViewGroup.LayoutParams) { - delegate.setContentView(view, params) - } - - override fun addContentView(view: View, params: ViewGroup.LayoutParams) { - delegate.addContentView(view, params) - } - - override fun onPostResume() { - super.onPostResume() - delegate.onPostResume() - } - - override fun onTitleChanged(title: CharSequence, color: Int) { - super.onTitleChanged(title, color) - delegate.setTitle(title) - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - delegate.onConfigurationChanged(newConfig) - } - - override fun onStop() { - super.onStop() - delegate.onStop() - } - - override fun onDestroy() { - super.onDestroy() - delegate.onDestroy() - } - - override fun invalidateOptionsMenu() { - delegate.invalidateOptionsMenu() - } - - private val delegate: AppCompatDelegate - get() { - if (mDelegate == null) { - mDelegate = AppCompatDelegate.create(this, null) - } - return mDelegate!! - } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/settings/GeneralSettingsActivity.kt b/app/src/main/java/io/neoterm/ui/settings/GeneralSettingsActivity.kt index f889aa2..889d27d 100644 --- a/app/src/main/java/io/neoterm/ui/settings/GeneralSettingsActivity.kt +++ b/app/src/main/java/io/neoterm/ui/settings/GeneralSettingsActivity.kt @@ -1,8 +1,8 @@ package io.neoterm.ui.settings -import androidx.appcompat.app.AlertDialog import android.os.Bundle import android.view.MenuItem +import androidx.appcompat.app.AlertDialog import io.neoterm.R import io.neoterm.frontend.config.NeoPreference import io.neoterm.utils.PackageUtils @@ -12,58 +12,58 @@ import io.neoterm.utils.PackageUtils */ class GeneralSettingsActivity : BasePreferenceActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - supportActionBar?.title = getString(R.string.general_settings) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - addPreferencesFromResource(R.xml.setting_general) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + supportActionBar?.title = getString(R.string.general_settings) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + addPreferencesFromResource(R.xml.setting_general) - val currentShell = NeoPreference.getLoginShellName() - findPreference(getString(R.string.key_general_shell)).setOnPreferenceChangeListener { _, value -> - val shellName = value.toString() - val newShell = NeoPreference.findLoginProgram(shellName) - if (newShell == null) { - requestInstallShell(shellName, currentShell) - } else { - postChangeShell(shellName) - } - return@setOnPreferenceChangeListener true - } + val currentShell = NeoPreference.getLoginShellName() + findPreference(getString(R.string.key_general_shell)).setOnPreferenceChangeListener { _, value -> + val shellName = value.toString() + val newShell = NeoPreference.findLoginProgram(shellName) + if (newShell == null) { + requestInstallShell(shellName, currentShell) + } else { + postChangeShell(shellName) + } + return@setOnPreferenceChangeListener true } + } - private fun postChangeShell(shellName: String) { - NeoPreference.setLoginShellName(shellName) - } + private fun postChangeShell(shellName: String) { + NeoPreference.setLoginShellName(shellName) + } - private fun requestInstallShell(shellName: String, currentShell: String) { - AlertDialog.Builder(this) - .setTitle(getString(R.string.shell_not_found, shellName)) - .setMessage(R.string.shell_not_found_message) - .setPositiveButton(R.string.install, { _, _ -> - PackageUtils.apt(this, "install", arrayOf("-y", shellName), { exitStatus, dialog -> - if (exitStatus == 0) { - dialog.dismiss() - postChangeShell(shellName) - } else { - dialog.setTitle(getString(R.string.error)) - } - }) - }) - .setNegativeButton(android.R.string.no, null) - .setOnDismissListener { - postChangeShell(currentShell) - } - .show() - } + private fun requestInstallShell(shellName: String, currentShell: String) { + AlertDialog.Builder(this) + .setTitle(getString(R.string.shell_not_found, shellName)) + .setMessage(R.string.shell_not_found_message) + .setPositiveButton(R.string.install, { _, _ -> + PackageUtils.apt(this, "install", arrayOf("-y", shellName), { exitStatus, dialog -> + if (exitStatus == 0) { + dialog.dismiss() + postChangeShell(shellName) + } else { + dialog.setTitle(getString(R.string.error)) + } + }) + }) + .setNegativeButton(android.R.string.no, null) + .setOnDismissListener { + postChangeShell(currentShell) + } + .show() + } - override fun onBuildHeaders(target: MutableList

?) { - } + override fun onBuildHeaders(target: MutableList
?) { + } - override fun onOptionsItemSelected(item: MenuItem?): Boolean { - when (item?.itemId) { - android.R.id.home -> - finish() - } - return super.onOptionsItemSelected(item) + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + when (item?.itemId) { + android.R.id.home -> + finish() } + return super.onOptionsItemSelected(item) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/settings/SettingActivity.kt b/app/src/main/java/io/neoterm/ui/settings/SettingActivity.kt index 617ae15..889052e 100644 --- a/app/src/main/java/io/neoterm/ui/settings/SettingActivity.kt +++ b/app/src/main/java/io/neoterm/ui/settings/SettingActivity.kt @@ -9,21 +9,21 @@ import io.neoterm.R */ class SettingActivity : BasePreferenceActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - supportActionBar?.title = getString(R.string.settings) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - addPreferencesFromResource(R.xml.settings_main) - } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + supportActionBar?.title = getString(R.string.settings) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + addPreferencesFromResource(R.xml.settings_main) + } - override fun onBuildHeaders(target: MutableList
?) { - } + override fun onBuildHeaders(target: MutableList
?) { + } - override fun onOptionsItemSelected(item: MenuItem?): Boolean { - when (item?.itemId) { - android.R.id.home -> - finish() - } - return super.onOptionsItemSelected(item) + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + when (item?.itemId) { + android.R.id.home -> + finish() } + return super.onOptionsItemSelected(item) + } } \ No newline at end of file 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 5bc8ca8..d0d2c81 100644 --- a/app/src/main/java/io/neoterm/ui/settings/UISettingsActivity.kt +++ b/app/src/main/java/io/neoterm/ui/settings/UISettingsActivity.kt @@ -9,21 +9,21 @@ import io.neoterm.R */ class UISettingsActivity : BasePreferenceActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - supportActionBar?.title = getString(R.string.ui_settings) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - addPreferencesFromResource(R.xml.settings_ui) - } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + supportActionBar?.title = getString(R.string.ui_settings) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + addPreferencesFromResource(R.xml.settings_ui) + } - override fun onBuildHeaders(target: MutableList
?) { - } + override fun onBuildHeaders(target: MutableList
?) { + } - override fun onOptionsItemSelected(item: MenuItem?): Boolean { - when (item?.itemId) { - android.R.id.home -> - finish() - } - return super.onOptionsItemSelected(item) + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + when (item?.itemId) { + android.R.id.home -> + finish() } + return super.onOptionsItemSelected(item) + } } \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/setup/SetupActivity.kt b/app/src/main/java/io/neoterm/ui/setup/SetupActivity.kt index 7cdc314..4134d14 100644 --- a/app/src/main/java/io/neoterm/ui/setup/SetupActivity.kt +++ b/app/src/main/java/io/neoterm/ui/setup/SetupActivity.kt @@ -28,234 +28,234 @@ import java.io.File * @author kiva */ class SetupActivity : AppCompatActivity(), View.OnClickListener, ResultListener { - companion object { - private const val REQUEST_SELECT_PARAMETER = 520; + companion object { + private const val REQUEST_SELECT_PARAMETER = 520; + } + + private var aptUpdated = false + private var setupParameter = "" + private var setupParameterUri: Uri? = null + + private val hintMapping = arrayOf( + R.id.setup_method_online, R.string.setup_hint_online, + R.id.setup_method_local, R.string.setup_hint_local, + R.id.setup_method_backup, R.string.setup_hint_backup + ) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.ui_setup) + + val parameterEditor = findViewById(R.id.setup_source_parameter) + + val tipText = findViewById(R.id.setup_url_tip_text) + + val onCheckedChangeListener = CompoundButton.OnCheckedChangeListener { button, checked -> + if (checked) { + val id = button.id + val index = hintMapping.indexOf(id) + if (index < 0 || index % 2 != 0) { + parameterEditor.setHint(R.string.setup_input_source_parameter) + return@OnCheckedChangeListener + } + parameterEditor.setHint(hintMapping[index + 1]) + tipText.setText(hintMapping[index + 1]) + setDefaultValue(parameterEditor, id) + } } - private var aptUpdated = false - private var setupParameter = "" - private var setupParameterUri: Uri? = null + findViewById(R.id.setup_method_online).setOnCheckedChangeListener(onCheckedChangeListener) + findViewById(R.id.setup_method_local).setOnCheckedChangeListener(onCheckedChangeListener) + findViewById(R.id.setup_method_backup).setOnCheckedChangeListener(onCheckedChangeListener) - private val hintMapping = arrayOf( - R.id.setup_method_online, R.string.setup_hint_online, - R.id.setup_method_local, R.string.setup_hint_local, - R.id.setup_method_backup, R.string.setup_hint_backup - ) + findViewById