Compare commits

...

653 Commits

Author SHA1 Message Date
agnostic-apollo
a6ae656c9f Bump to v0.110 2021-04-15 05:14:31 +05:00
agnostic-apollo
3af5730354 Add support for allowing IDE to show documentation in .gradle files 2021-04-15 05:03:44 +05:00
agnostic-apollo
3306c3c2a2 Add support to include source jar files for libraries published by termux 2021-04-15 05:01:41 +05:00
agnostic-apollo
cde0bd2246 Prevent DebuggingPreferencesFragment and TerminalIOPreferencesFragment from being removed during minification
Fixes #2005
2021-04-15 02:15:55 +05:00
agnostic-apollo
354fe1948e Merge pull request #2002 from agnostic-apollo/termux-various-fixes-and-improvements 2021-04-13 17:18:27 +05:00
agnostic-apollo
ae1c9bacd6 Bump to v0.109 2021-04-13 16:58:18 +05:00
agnostic-apollo
d19cf435be Add ".sh" extension to apt_info_script file 2021-04-13 16:57:24 +05:00
agnostic-apollo
1132028bd2 Add TermuxTerminalViewClientBase to com.termux.shared.terminal package and extend that in TermuxTerminalViewClient 2021-04-13 16:34:24 +05:00
agnostic-apollo
b7b4a0a8a2 Move TermuxTerminalSessionClientBase to com.termux.shared.terminal package 2021-04-13 16:20:47 +05:00
agnostic-apollo
ffd8687b4c Revert "Bump to v0.109" 2021-04-13 16:09:31 +05:00
Leonid Pliushch
bbb6f4471f update bootstrap archives 2021-04-13 13:34:11 +03:00
agnostic-apollo
b33b906784 Merge pull request #1953 from agnostic-apollo/termux-various-fixes-and-improvements 2021-04-13 00:07:24 +05:00
agnostic-apollo
567eeca782 Bump to v0.109 2021-04-12 20:20:26 +05:00
agnostic-apollo
3d46849673 Replace "if(" with "if (" 2021-04-12 20:19:04 +05:00
agnostic-apollo
6293f5f170 Added APT package info when generating "Report Issue" text
This will now take a few more seconds due to "apt update" command being run.
2021-04-12 19:22:29 +05:00
agnostic-apollo
e5c5174f6f Fix TermuxActivityBroadcastReceiver wrongly designed intent actions and extras
From now on
- TERMUX_ACTIVITY.ACTION_REQUEST_PERMISSIONS should be used for requesting storage permissions.
- TERMUX_ACTIVITY.ACTION_RELOAD_STYLE should be used for reloading styling.
- TERMUX_ACTIVITY.EXTRA_RELOAD_STYLE which was previously used for requesting storage permissions if its value equaled "storage" has been deprecated.

If more actions need to be supported in future, add them to TermuxActivity.registerTermuxActivityBroadcastReceiver() IntentFilter.
2021-04-12 19:22:07 +05:00
agnostic-apollo
f1034c2e79 Add support for stdin for background RUN_COMMAND intent and TERMUX_SERVICE.ACTION_SERVICE_EXECUTE commands 2021-04-12 19:16:45 +05:00
agnostic-apollo
bbf03a0507 The TermuxConstants class has been updated to v0.19.0. Check its Changelog section for info on changes. 2021-04-12 19:16:21 +05:00
agnostic-apollo
153818f7fb Fix typos 2021-04-12 18:26:18 +05:00
agnostic-apollo
192b208883 Add support for stdin for background TermuxTasks
This will allow passing scripts (to bash or python) or other data to an executable via stdin. Arguments are passed to the executable and not the script.
2021-04-12 18:25:43 +05:00
agnostic-apollo
824b3e657f Rename termux terminal view and session client classes
- com.termux.app.terminal.TermuxViewClient has been renamed to TermuxTerminalViewClient
- com.termux.app.terminal.TermuxSessionClient has been renamed to TermuxTerminalSessionClient
- com.termux.shared.shell.TermuxSessionClientBase has been renamed to TermuxTerminalSessionClientBase

This was required because com.termux.app.terminal.TermuxSessionClient was in conflict with com.termux.shared.shell.TermuxSessionClient interface of com.termux.shared.shell.TermuxSession.
2021-04-12 15:19:25 +05:00
agnostic-apollo
a95e187b25 Assure "Process completed" string is written to terminal before session is finished 2021-04-12 14:49:49 +05:00
agnostic-apollo
f888f35e35 Fix issue where TermuxService was stopped and Termux notification canceled, if background tasks were still running but all sessions had been closed. 2021-04-12 14:43:39 +05:00
agnostic-apollo
24a5493ea5 Replace "if(" with "if (" 2021-04-12 14:32:02 +05:00
agnostic-apollo
0cd7cb8162 Improve TermuxSession and TermuxTask
TermuxSession, TermuxTask and TermuxSessionClientBase have been moved to termux-shared. They are now not dependent on TermuxService anymore and have become abstract so that they can be called from anywhere. TermuxSession.TermuxSessionClient and TermuxTask.TermuxTaskClient interfaces have been created for callbacks, which the TermuxService now implements.

The TermuxTask now also supports synchronous command execution as well to run shell commands from anywhere for internal use by termux app and its plugins.

TermuxTask now also supports killing the process being run by sending it a SIGKILL. This is used by TermuxService to kill all the TermuxTasks (in addition to TermuxSessions) it manages when it is destroyed, either by user exiting it or by android killing it. Only the tasks that were started by a plugin which **expects** the result back via a pending intent will be killed, but the remaining background tasks will keep on running until the termux app process is killed by android, like by OOM. Check TermuxService.killAllTermuxExecutionCommands() for more details on how TermuxService kills TermuxTasks and TermuxSessions.

Fixed null pointer exception when getting terminal transcript if TerminalEmulator not initialized.
2021-04-12 14:26:53 +05:00
agnostic-apollo
df4d8ac7e5 Add support for reporting issues to the termuxreports@groups.io email 2021-04-11 02:29:00 +05:00
agnostic-apollo
64fb2ce49b Update TermuxConstants
The `TermuxConstants` class has been updated to `v0.18.0`. Check its Changelog section for info on changes.
2021-04-11 02:28:00 +05:00
agnostic-apollo
0c9b85a4f9 Micro optimization for StreamGobbler 2021-04-10 02:32:21 +05:00
agnostic-apollo
62a2104adc Updated RunCommandService javadocs.
Documentation has been moved to https://github.com/termux/termux-app/wiki/RUN_COMMAND-Intent
2021-04-08 03:26:43 +05:00
agnostic-apollo
71dfefd4b7 Add termux-shared library to github workflow so that its published 2021-04-07 11:32:22 +05:00
agnostic-apollo
682ce08314 Create termux-shared library package for all termux constants and shared utils
The termux plugins should use this library instead of hardcoding "com.termux" values in their source code.

The library can be included as a dependency by plugins and third party apps by including the following line in the build.gradle where x.xxx is the version number, once its published.

`implementation 'com.termux:termux-shared:x.xxx'`

The `TermuxConstants` class has been updated to `v0.17.0`, `TermuxPreferenceConstants` to `v0.9.0` and `TermuxPropertyConstants` to `v0.6.0`. Check their Changelog sections for info on changes.

Some typos and redundant code has also been fixed.
2021-04-07 11:31:30 +05:00
agnostic-apollo
c9a476caf7 Add TermuxTaskerAppSharedPreferences to handle termux-tasker shared preferences 2021-04-06 17:23:12 +05:00
agnostic-apollo
9cee71004f Replace "if(" with "if (" 2021-04-06 17:17:12 +05:00
agnostic-apollo
81d97c3584 Fix potential TransactionTooLargeException when sharing text 2021-04-06 17:13:28 +05:00
agnostic-apollo
939338aaac Allow users to report an issue from terminal transcript by selection "Report Issue" from context menu 2021-04-06 17:09:54 +05:00
agnostic-apollo
067709bf4b Disable obfuscation of termux-app in release builds without disabling optimizations and shrinking
This will allow stacktraces to have full class and method names when a crash report is generated.
2021-04-06 16:15:50 +05:00
agnostic-apollo
69e4b575a8 Implement crash handler and reporting
Now whenever the Termux app crashes, the crash report (stacktrace, app and device info) will be logged to ~/crash_log.md file. When the user will reopen the app, a notification will be shown which when clicked will show the crash report content in the ReportActivity. The activity will have important links like email, reddit, github issues of termux app and packages at which the user can optionally report an issue if necessary after copying the crash report text. The ~/crash_log.md file will be moved to ~/crash_log-backup.md so that a notification is not shown again on next startup and can be viewed again via SAF, etc.

This will allow reports for bugs that are submitted to have complete and useful info, specially in markdown format, making lives of devs a tad bit easier. Also more bugs that are rare might be submitted since users will have the info to report with and know where to report at.

ToDo:
- The TermuxConstants.TERMUX_SUPPORT_EMAIL_URL needs to be updated with a valid support email once its set up. The TermuxUtils.getReportIssueMarkdownString() function currently also has "email" lines commented out which will need to be uncommented.
- Currently, crashes will only be handled for the main app thread, other threads will have to manually hooked into where necessary.
2021-04-06 16:15:00 +05:00
agnostic-apollo
07e6ecd3c3 Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.16.0`. Check its Changelog section for info on changes.
2021-04-06 16:13:28 +05:00
agnostic-apollo
325a6f7d66 Fix SettingsActivity shortcut not showing on android 7 and fixed shortcut actions and categories 2021-04-06 13:27:43 +05:00
agnostic-apollo
cf5bb69fc8 Add crash_report_notifications_enabled shared preferences
This will allow users to control if a notification should be shown with the crash info when app is restarted after a crash

The `TermuxPreferenceConstants` classes has been updated to `v0.8.0`. Check its Changelog section for info on changes.
2021-04-06 12:48:56 +05:00
agnostic-apollo
18b004a2ba Fix Logger function modifier order 2021-04-06 12:23:00 +05:00
agnostic-apollo
38323b1c2a Fix RUN_COMMAND Intent plugin api url 2021-04-06 12:21:22 +05:00
agnostic-apollo
8a5442f80d Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.15.0`. Check its Changelog sections for info on changes.
2021-04-06 12:19:37 +05:00
agnostic-apollo
8598b92dea Use FileUtils for clearing TMPDIR and move clearing function to ShellUtils from TermuxService 2021-04-06 06:00:41 +05:00
agnostic-apollo
b2cd20c035 Use FileUtils for bootstrap and shared storage symlinks setup 2021-04-06 06:00:21 +05:00
agnostic-apollo
d4fc34ca2d Move FileUtils to file package and define more file util functions
A lot of utils have been defined now that can be used to safely manage files.

The java java.io.File API has poor support for detecting symlinks including broken symlinks. Android implementation also has issues. Check FileTypes.getFileType() function for more info. For this reason, the UnixFileAttributes and related classes has been ported from AOSP to get file attributes and type.

Some file utils and android versions use google's Guava com.google.common.io.MoreFiles library for managing files, specially for safer directory deletion with SecureDirectoryStream.

Some file utils and android versions use org.apache.commons.io.FileUtils for managing files. The library version used is 2.5 and it must not be incremented for compatibility with android version < 8, otherwise runtime crashes will occur.
2021-04-06 06:00:05 +05:00
agnostic-apollo
c0323fe816 Move ShellUtils to shell package 2021-04-05 23:26:47 +05:00
agnostic-apollo
a32309827f Update gradle version to 4.1.3 2021-04-05 23:22:04 +05:00
agnostic-apollo
ada678dfe2 Move get*LogStringEntry() functions to logger class 2021-04-05 23:21:13 +05:00
agnostic-apollo
cdbd38faaa Move StreamGobbler to shell package 2021-04-05 23:18:43 +05:00
agnostic-apollo
d4653d0590 Get TERMUX_VERSION while building shell environment via Termux package context 2021-03-29 07:20:39 +05:00
agnostic-apollo
49f53f55f3 Renamed TextDataUtils to DataUtils 2021-03-28 09:08:45 +05:00
agnostic-apollo
15eb56d4dd Add PackageUtils and fix ReportActivity
- PackageUtils has been added to get various package related info. This will be used to get info based on Context objects instead of using BuildConfig which wouldn't have been available across termux plugins.

- Support for getting Context objects of all termux plugin apps have been added to TermuxUtils.

- Support for showing more details for the app has been added for ReportActivity. This will also allow app info of Termux app to be generated when TermuxUtils.getAppInfoMarkdownString() is called by a termux plugin so that both are shown so that devs/users can more easily detect compatibility issues.

- ReportActivity has been fixed to also include report and device info instead of just the ExecutionCommand info when copying and sharing.

- Moved the generation of markdown for ReportInfo to its own class and added creationTimestamp field.

- Increased markdown headings size for some cases.
2021-03-28 09:06:17 +05:00
agnostic-apollo
d7ea770d47 Move com.termux.models to com.termux.app.models package 2021-03-27 19:08:26 +05:00
agnostic-apollo
2a8d5e292d Add support for disabling soft keyboard completely with the "soft_keyboard_enabled" SharedPreferences key
Users can toggle the state from Settings -> Keyboard I/O -> Soft Keyboard toggle.

Android phone should also have an internal setting for disabling soft keyboard when a hardware keyboard is connected in Language and Input android settings or from the input mode selection notification, but the above setting will be Termux app specific and will allow soft keyboard to still be shown in other apps.

The `TermuxPreferenceConstants` classes has been updated to `v0.7.0`. Check its Changelog section for info on changes.
2021-03-27 07:55:21 +05:00
agnostic-apollo
04da4b2268 Send PendingIntent back for pre-execution errors generated for RUN_COMMAND intent
If the pending intent is not null, then the errors will be sent back to the caller without notifying the user, since we will let the caller handle the errors himself. They will still be logged in logcat.

However, if "allow-external-apps" is not true, then a flash and notification will be shown forcefully (regardless of "Plugin Execution Errors" toggle state), so that the user knows someone tried to run a command in termux context, since it may be malicious app or imported (tasker) plugin project and not the user himself. If a pending intent is also sent, then its creator is also logged and shown.
2021-03-26 16:33:46 +05:00
agnostic-apollo
34bacfd5b1 Fix terminal-emulator constructor used in tests 2021-03-26 02:11:09 +05:00
agnostic-apollo
006d5abb78 Disable markdown text selection for now since it stops URL clicks from working 2021-03-26 01:26:53 +05:00
agnostic-apollo
480bad181b Use TermuxConstants for getting urls instead of using hardcoded strings 2021-03-26 01:25:15 +05:00
agnostic-apollo
2afa4b4351 Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.14.0`. Check its Changelog sections for info on changes.
2021-03-25 23:30:36 +05:00
agnostic-apollo
a2209ddd5e Add support for sending back background and foreground command results for RUN_COMMAND intent and foreground command results for Termux:Tasker
Previously, termux only supported getting result of BACKGROUND commands back if they were started via Termux:Tasker plugin. Getting back result of foreground commands was not possible with any way.

Now with RUN_COMMAND intent or Termux:Tasker, the third party apps and users can get the foreground command results as well. Note that by "foreground results" we only mean the session transcript. The session transcript will contain both stdout and stderr combined, basically anything sent to the the pseudo terminal /dev/pts, including PS1 prefixes for interactive sessions. Getting separate stdout and stderr can currently only be done with background commands.

Moreover, with RUN_COMMAND intent, third party apps and users can get the background commands results as well. This means separate extras for stdout and stderr.

The exit code will also be returned for either case.

### RUN_COMMAND intent

The result extras are returned in the TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE bundle via the pending intent received.

The RUN_COMMAND_SERVICE.EXTRA_PENDING_INTENT extra can be used to send the pending intent with which termux should return the result bundle. The pending intent can be received back by the app with an IntentService. Check RunCommandService for reference implementation.

For foreground commands (RUN_COMMAND_SERVICE.EXTRA_BACKGROUND is false):
- EXTRA_PLUGIN_RESULT_BUNDLE_STDOUT will contain session transcript.
- EXTRA_PLUGIN_RESULT_BUNDLE_STDERR will be null since its not used.
- EXTRA_PLUGIN_RESULT_BUNDLE_EXIT_CODE will contain exit code of session.

For background commands (RUN_COMMAND_SERVICE.EXTRA_BACKGROUND is true):
- EXTRA_PLUGIN_RESULT_BUNDLE_STDOUT will contain stdout of commands.
- EXTRA_PLUGIN_RESULT_BUNDLE_STDERR will contain stderr of commands.
- EXTRA_PLUGIN_RESULT_BUNDLE_EXIT_CODE will contain exit code of command.

The internal errors raised by termux outside the shell will be sent in the the EXTRA_PLUGIN_RESULT_BUNDLE_ERR and EXTRA_PLUGIN_RESULT_BUNDLE_ERRMSG extras. These will contain errors like if starting a termux command failed or if the user manually exited the termux sessions or android killed the termux service before the commands had finished executing. The err value will be Activity.RESULT_OK(-1) if no internal errors are raised.

The stdout and stderr will be truncated from the start to max 100KB combined and errmsg will also be truncated from end to max 25KB. This is necessary to prevent TransactionTooLargeException exceptions from being raised if stdout or stderr are too large in length. The original length of stdout and stderr will be provided in EXTRA_PLUGIN_RESULT_BUNDLE_STDOUT_ORIGINAL_LENGTH and EXTRA_PLUGIN_RESULT_BUNDLE_STDERR_ORIGINAL_LENGTH extras respectively, so that the caller can check if either of them were truncated.

### Termux:Tasker

Support for Termux:Tasker for getting back result of foreground commands will require an update to it since it currently immediately returns control to plugin host app like Tasker without waiting if a foreground command is to be executed.
2021-03-25 23:05:55 +05:00
agnostic-apollo
2cc6285a81 Remove leftover log entry from TermuxSessionClient 2021-03-25 23:01:45 +05:00
agnostic-apollo
5dee839230 Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.13.0`. Check its Changelog sections for info on changes.
2021-03-25 21:17:54 +05:00
agnostic-apollo
1ef8eb9219 Add hide-soft-keyboard-on-startup property
If its set to `true` in termux.properties file, then soft keyboard will automatically be hidden on Termux App start to solve issues for when users use hardware keyboard and soft keyboard is automatically opened and wastes terminal screen space.

The `TermuxPropertyConstants` classes has been updated to `v0.5.0`. Check its Changelog sections for info on changes.

Fixes #1978
2021-03-25 12:30:59 +05:00
agnostic-apollo
d3ddb21716 Warn users if Draw over other apps permission is missing
For android version >= 10(Q), a flash will be shown to users requesting them to grant the permission if they attempt to start a foreground terminal session command from background, like with the RUN_COMMAND intent. The flash will only be shown if "Plugin Error Notifications" toggle is enabled in settings.
2021-03-25 12:04:09 +05:00
agnostic-apollo
8e80e889f0 Set compileSdkVersion (NOT targetSdkVersion) to 29 so that Build.VERSION_CODES.Q checks can be made 2021-03-25 11:52:26 +05:00
agnostic-apollo
4e5d14e4a2 Use NotificationUtils to handle TermuxService and RunCommandService notifications 2021-03-25 11:00:00 +05:00
agnostic-apollo
0230698494 Fix unintentional wordwrap of ExecutionCommand arguments markdown 2021-03-25 10:11:43 +05:00
agnostic-apollo
977cb34fc7 Provide better errmsg if executable passed to RunCommandService is null or empty
Previously, the null or empty executable would be expanded to the literal root "/" string path by FileUtils.getCanonicalPath and then FileUtils.validateRegularFileExistenceAndPermissions() validation would fail since path will not be a regular file. So a user will be shown that "/" is not a regular file. Now we show that executable was not even passed.
2021-03-25 10:10:19 +05:00
agnostic-apollo
f62febbfb7 Add the TermuxTask class for linking a Process to an ExecutionCommand.
TermuxTask will maintain info for background Termux tasks. Each task started by TermuxService will now be linked to a ExecutionCommand that started it.

- StreamGobbler class has also been imported from https://github.com/Chainfire/libsuperuser and partially modified to read stdout and stderr of background commands. This should likely be much safer and efficient.
- Logging of every line has been disabled unless log level is set to verbose. This should have a performance increase and also prevent potentially private user data to be sent to logcat.
- This also solves the bug where Termux:Tasker would hang indefinitely if Runtime.getRuntime().exec raised an exception, like for invalid or missing interpreter errors and Termux:Tasker wasn't notified of it. Now the errmsg will be used to send any exceptions back to Termux:Tasker and other 3rd party calls.
- This also solves the bug where stdout or stderr were too large in size and TransactionTooLargeException exception was raised and result TERMUX_SERVICE.EXTRA_PENDING_INTENT pending intent failed to be sent to the caller. This would have also hung up Termux:Tasker. Now the stdout and stderr sent back in TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE bundle will be truncated from the start to max 100KB combined. The original size of stdout and stderr will be provided in TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE_STDOUT_ORIGINAL_LENGTH and TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE_STDERR_ORIGINAL_LENGTH extras respectively so that the caller can check if either of them were truncated. The errmsg will also be truncated from end to max 25KB to preserve start of stacktraces.
- The PluginUtils.processPluginExecutionCommandResult() has been updated to fully handle the result of plugin execution intents.
2021-03-25 09:47:59 +05:00
agnostic-apollo
7ca20fdeb3 Update ExecutionCommand to make current and previous state private to prevent direct modification
- Helper functions are now provided to check for common states. The currentState and previousState must only be modified via setState()
- Some errCode values are also provided to prevent hardcoded value usage.
- The stdout, stderr and arguments will now be truncated for logcat to honour its LOGGER_ENTRY_MAX_LEN limits
2021-03-25 09:20:09 +05:00
agnostic-apollo
922f4f4ae5 Update TextDataUtils.getTruncatedCommandOutput() to support truncation from start or end and for (truncated) prefix 2021-03-25 09:08:45 +05:00
agnostic-apollo
df03f0b7d6 Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.12.0`. Check its Changelog sections for info on changes.
2021-03-25 09:02:59 +05:00
agnostic-apollo
249f7c6b7c Remove BackgroundJob 2021-03-25 09:02:35 +05:00
agnostic-apollo
78a99fddfd Add the TermuxSession class for linking a TerminalSession to an ExecutionCommand.
TermuxSession will maintain info for foreground Termux sessions. Each terminal session started by TermuxService will now be linked to a ExectionCommand that started it.

This also fixes bugs where newly created session in some cases were not being automatically selected and scrolled to, like when adding a named or failsafe session or those which were created for executable intents.
2021-03-25 01:18:13 +05:00
agnostic-apollo
dff2794501 Add ShellUtils to define shell related utils since they don't belong in BackgroundJob 2021-03-25 01:13:51 +05:00
agnostic-apollo
3e0f74a894 Fixed keep screen on default value
The `TermuxPreferenceConstants` classes has been updated to `v0.6.0`. Check its Changelog sections for info on changes.
2021-03-24 06:52:17 +05:00
agnostic-apollo
2b3f681723 Add SettingsActivity to launcher shortcuts 2021-03-24 06:48:47 +05:00
agnostic-apollo
d3d6731d6f Fix typos 2021-03-24 06:47:12 +05:00
agnostic-apollo
9bbcc08ff3 Fix variable prefix for intent extra 2021-03-24 06:33:09 +05:00
agnostic-apollo
7aa957c684 Fix typos 2021-03-24 06:30:26 +05:00
agnostic-apollo
eeb8554535 Fix string resources naming convention 2021-03-24 06:15:45 +05:00
agnostic-apollo
4eced52c5f Fix xml files naming convention 2021-03-24 05:28:38 +05:00
agnostic-apollo
b856e16998 Move activities and fragments to respective packages
Move com.termux.TermuxSettingsActivity to com.termux.app.activities.SettingsActivity
Move com.termux.TermuxHepActivity to com.termux.app.activities.HelpActivity
Move com.termux.settings.DebuggingPreferencesFragment to com.termux.app.fragments.settings.DebuggingPreferencesFragment
2021-03-24 05:13:00 +05:00
agnostic-apollo
1bdf9bf2e3 Change log level to warn from error when termux.properties file is missing 2021-03-24 04:57:35 +05:00
agnostic-apollo
92b804dc9c Add logging for termux bootstrap package installation and setup of storage symlinks 2021-03-24 04:55:10 +05:00
agnostic-apollo
1b5e5b56cb Partially integrate ExectionCommand into TermuxService and BackgroundJob
The TERMUX_SERVICE.ACTION_SERVICE_EXECUTE intent received will be managed by the ExectionCommand now.
The cwd and failsafe have been renamed to workingDirectory and isFailsafe.
2021-03-24 03:59:18 +05:00
agnostic-apollo
31371b5e3d Fully integrate ExectionCommand into RunCommandService
Users will now also be shown flashes and notifications in addition to log entries for missing allow-external-apps permission or for invalid extras passed like the executable. The flashes and notifications can be controlled with the Termux Settings -> Debugging -> Plugin Error Notifications toggle
2021-03-24 03:56:25 +05:00
agnostic-apollo
ef1ab197b6 Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.11.0`. Check its Changelog sections for info on changes.
2021-03-24 03:51:57 +05:00
agnostic-apollo
bccc35bc3f Added ExectionCommand
ExectionCommand is a class that stores all data related to an execution command like:

- Input parameters like executable and arguments to be used to run the shell command, etc
- Output parameters like stdout, stderr and exitCode.
- Error info generated internally by termux outside the shell in errCode and errmsg.
- Command info like, id, label, description, help info, etc.
- Other config info like for how termux should handle the command.
- The pending intent if any that should be sent after execution to command requester.
- The help for the plugin API that was used to send the intent.
- Current and previous state of the command.

This allow easier management and passing of execution command data between classes and management of it. This will later allow each ExectionCommand command to be linked to a Terminal Session, to handle post processing and failure management.
The ExectionCommand also provides functions to get its data in markdown format, which can be used by failure or success reports generated for the command that are shown to the user. The commandHelp and pluginAPIHelp can also be specially useful to provide info to users on how to manage failures that are generated.
2021-03-24 03:48:24 +05:00
agnostic-apollo
20d20f42c0 Added NotificationUtils to build and manage notifications
The `TermuxPreferenceConstants` classes has been updated to `v0.5.0`. Check its Changelog sections for info on changes.
2021-03-24 03:25:25 +05:00
agnostic-apollo
9d36e9adde Add ReportActivity and ReportInfo
This implements the framework to report info to users. This may include reporting failure or result of commands or any exceptions that are raised.

The ReportInfo provides 5 fields:
- userAction: The user action that was being processed for which the report was generated.
- sender: The internal app component that sent the report.
- title: The report title.
- reportString: The markdown text for the report.
- addReportAndDeviceDetails: If set to true, then report and device details will be added to the report.

This should provide the basics parameters for showing a report to the user. The ReportActivity also allows user to copy and share the report.

In future this can also be used to allow users to easily email or post crash reports to github for Termux app crashes instead of going through logcat.
2021-03-24 03:22:12 +05:00
agnostic-apollo
3491956385 Update TermuxUtils to add support for getting device and termux info as markdown 2021-03-24 02:38:37 +05:00
agnostic-apollo
134e21765c Update Logger
- Add support to log Throwables instead of just Exception class objects.
- Adds utility functions to get Throwables as Strings and Markdown Strings.
2021-03-24 02:34:53 +05:00
agnostic-apollo
c28990a176 Add ShareUtils 2021-03-24 02:31:22 +05:00
agnostic-apollo
131f481750 Add commonmark-spec markdown support with markwon library
Also adds MarkdownUtitls to provide various utils for markdown processing.
2021-03-24 02:30:20 +05:00
agnostic-apollo
f393e9b2cf Allow mPropertiesList to be null for SharedProperties
This will allow all keys that are read from file to be stored in mMap without knowing their names beforehand.
2021-03-24 02:27:28 +05:00
agnostic-apollo
8612a1d0f8 Added plugin_error_notifications_enabled preference
This will allow user to control whether flashes and notifications for plugin errors are enabled or not.

The `TermuxPreferenceConstants` classes has been updated to `v0.4.0`. Check its Changelog sections for info on changes.
2021-03-19 22:49:06 +05:00
agnostic-apollo
7d53a147d8 Reordered TermuxService.actionAcquireWakeLock() 2021-03-19 21:18:39 +05:00
agnostic-apollo
5d6a98452a Added TERMUX_SERVICE#ACTION_SERVICE_EXECUTE Intent Logging 2021-03-19 21:18:37 +05:00
agnostic-apollo
b4995ef9a7 Refactor RunCommandService
- The `FileUtils` and `PluginUtils` have been added to provide utility functions.
- The executable and working directory validation has been added to check for existence and missing permissions.
- The `expandPath()` function is removed from `RunCommandService`.
- Working directory will automatically be created if under `TermuxConstants.TERMUX_FILES_DIR_PATH` if missing.
- Better logging has been added. This will later be used to notify the user in foreground.
- Javadocs have been updated.
2021-03-19 21:18:35 +05:00
agnostic-apollo
ec7568d28e Add support for session actions for foreground session commands
The `TERMUX_SERVICE.EXTRA_SESSION_ACTION` extra can be passed to define what should happen when a foreground session
command is received for the `TERMUX_SERVICE.ACTION_SERVICE_EXECUTE` intent to `TermuxService`, like from `RunCommandService` or `Termux:Tasker`. The user can define whether the new session should be automatically switched to or if existing session should remain as the current session. The user can also define if foreground session commands should open the `TermuxActivity` or if they should run in the "background" in the Termux notification. The user can click the notification to open the sessions. Check `TERMUX_SERVICE.VALUE_EXTRA_SESSION_ACTION*` values to see various behaviors.

This also solves the old "issue" that if a foreground command was received while an existing session was already in the foreground, the new session won't be switched to automatically. It only brought the new session to the foreground if the activity was not already in foreground, since a call to `mTermuxSessionClient.setCurrentSession(newSession)` wasn't being made.
2021-03-19 21:18:33 +05:00
agnostic-apollo
607ba3ee56 Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.10.0`. Check its Changelog sections for info on changes.
2021-03-19 21:18:31 +05:00
agnostic-apollo
ae260fad9c Fix old bug where termux app would crash if sessions list ListView was not notified of new sessions
To reproduce:
1. Create 2 sessions.
2. From either session, run a random `RUN_COMMAND` intent command with `am` command and shift to the other session.

Termux app would crash and throw the `The content of the adapter has changed but ListView did not receive a notification.` exception. TermuxService was previously not notifying the ListView of the sessions list that a new session has been added, if the activity was in foreground.
2021-03-19 21:18:27 +05:00
agnostic-apollo
d9b5344b24 Refactor TermuxService
- Dedicated functions have been created for various actions and commands.
- The `startForeground()` call will be made both on `onCreate()` and `onStartCommand().
- The `stopForeground()` call will be made before every `onSelf()` call.
- The references to `TermuxActivity` will now be removed in `onUnbind()` as well if activity `onDestroy()` failed to do so.
- Appropriate log entries are added to help debug issues.
2021-03-19 21:18:25 +05:00
agnostic-apollo
bc02508102 Fix dependencies ordering 2021-03-19 21:18:24 +05:00
agnostic-apollo
f969c01f7e Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.9.0`. Check its Changelog sections for info on changes.
2021-03-19 21:18:21 +05:00
agnostic-apollo
fb6e9b69ab Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.8.0`. Check its Changelog sections for info on changes.
2021-03-19 21:18:13 +05:00
agnostic-apollo
c1a377d15d Update SharedPreferenceUtils
This SharedPreferenceUtils now supports:
- Getting `Context.MODE_PRIVATE` and/or `Context.MODE_MULTI_PROCESS` `SharedPreference` instances.
- Setting values to shared preferences in-memory cache and the file synchronously.
- Getting and setting `float`, `long` and `Set<Sting>` values in shared preferences.
2021-03-17 03:16:37 +05:00
agnostic-apollo
8c1c7ce727 Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.7.0`. Check its Changelog sections for info on changes.
2021-03-17 01:13:44 +05:00
agnostic-apollo
2a940dfca6 Move general static functions from TermuxSharedProperties to SharedProperties class
This will allow `SharedProperties` class to be used directly to get property values and their internal values via static functions instead of using `TermuxSharedProperties` where the functions don't belong in.

The `TermuxPropertyConstants` classes has been updated to `v0.4.0`. Check its Changelog sections for info on changes.
2021-03-16 22:52:20 +05:00
agnostic-apollo
2e7fd480f4 Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.6.0`. Check its Changelog sections for info on changes.
2021-03-16 21:29:25 +05:00
agnostic-apollo
9e82561804 Added SharedPreferenceUtils
The `SharedPreferenceUtils` class has been added to provide static util functions for shared preferences like get and set while safely handling exceptions. The `TermuxSharedPreferences` class has been renamed to `TermuxAppSharedPreferences` since its to be used to handle only Termux App related shared preferences and not of other plugin apps. For plugin apps, separate classes can be created. However, Termux app and its plugins will share the same `TermuxPreferenceConstants` class that now has per app scoping since `v0.3.0`.
2021-03-16 20:51:41 +05:00
agnostic-apollo
4427c1675d Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.5.0`. Check its Changelog sections for info on changes.
2021-03-16 19:07:54 +05:00
agnostic-apollo
6f6d11fbb8 Updated TermuxPreferenceConstants
The `TermuxPreferenceConstants` classes has been updated to `v0.3.0`. Check its Changelog sections for info on changes.
2021-03-16 19:06:17 +05:00
agnostic-apollo
ff0440d7d2 Allow users to adjust termux toolbar height with termux.properties
This `terminal-toolbar-height` key can be used to adjust the toolbar height. The user can set a float value between `0.4` and `3.0` which will be used as the scaling factor for the default height. The default scaling factor is `1`. So adding an entry like `terminal-toolbar-height=2.0` to `termux.properties` file will make the toolbar height twice its original height. Running `termux-reload-settings` command will also update the height instantaneously if changed.

Fixes #1857
2021-03-16 07:04:55 +05:00
agnostic-apollo
c9e18e5b93 Refactor TermuxActivity
This commit majorly refactors `TermuxActivity` and moves its view components and functions into dedicated classes.

- The view layouts and ids have been given meaningful names, like `termux_activity.xml`.
- The `TerminalToolbarViewPager` class has been created to handle the now called toolbar that shows on the bottom of the terminal view. It currently contains extra keys view defined by `terminal_toolbar_extra_keys_view.xml` file and a text input view defined by `terminal_toolbar_text_input_view.xml` file when user can switch to by swiping left. The input text will now be preserved if android destroys the activity or its recreated.
- The `TermuxSessionsListViewController` class has been created to handle view related functionality of the termux sessions list shown in the left drawer, namely view creation, `onItemClick()`, `onItemLongClick()`, etc. Its list view is defined by `termux_activity.xml` file and each item's layout is defined by the `terminal_sessions_list_item.xml` file.
- The `TextDataUtils` class has been added to the `com.termux.app.utils` package for text utils.
- The design for the `SessionChangedCallback` interface for `TerminalSession` has been majorly changed. Firstly, it has been renamed and moved from `TerminalSession` to the dedicated `TerminalSessionClient` class file. The interface now also supports the termux app centralized logging framework so that `TerminalSession` and `TerminalEmulator` can use them. Previously, `TermuxService` was implementing a wrapper interface, which would then call the real interface defined by the `TermuxActivity` if it was currently bound to the service. This cluttered and partially duplicated the code. Now, the implementation is defined by the `TermuxSessionClientBase` and `TermuxSessionClient` classes. The `TermuxSessionClientBase` implements the `TerminalSessionClient` interface but the definition of the activity related functions do not do anything, only the background ones like the logging functions are fully implemented. The `TermuxSessionClient` class inherits from the `TermuxSessionClientBase` class and provides the implementation for the activity related functions. The design for how this works is that if the `TermuxService` is not bound to `TermuxActivity`, it just passes the `TermuxSessionClientBase` implementation to `TerminalSession`. If the activity is bound at some point, then in `onServiceConnected()` it replaces/updates the client objects stored in `TerminalSession` and `TerminalEmulator` with `TermuxSessionClient`, and then replaces them back with `TermuxSessionClientBase` in `onDestroy()`. This seems to be working for now without an issue.
2021-03-16 05:01:09 +05:00
agnostic-apollo
5e0b29bb6d Move DialogUtils to com.termux.app.utils package 2021-03-16 04:07:01 +05:00
agnostic-apollo
73af5c2326 Move terminal io related classed to com.termux.app.terminal.io package 2021-03-16 04:05:15 +05:00
agnostic-apollo
7da847a485 Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.4.0`. Check its Changelog sections for info on changes.
2021-03-16 03:59:54 +05:00
agnostic-apollo
11a236a172 Update TerminalView and TerminalViewClient javadocs 2021-03-16 03:46:34 +05:00
agnostic-apollo
3b5d3114a6 Prefix DEFAULT_LOG_TAG even when a tag is passed while logging 2021-03-16 03:44:45 +05:00
agnostic-apollo
66f15d2a08 Remove moved files 2021-03-16 03:44:27 +05:00
agnostic-apollo
0225a8b1fc Fix hardcoded value in strings.xml 2021-03-16 03:41:17 +05:00
agnostic-apollo
d39972b3bf Implement GUI based Termux settings manager and a centralized logging framework
The settings activity can be accessed by long pressing on terminal view and selecting "Settings" from the popup shown. It uses the Android's Preference framework. Currently only debugging preferences to set log level and enabling terminal view key logging are provided. The Preference framework by default uses the keys set in `app:key` attribute in the respective preferences XML file to store the values in the default `SharedPreferences` file of the app. However, since we rely on `TermuxPreferenceConstants` and `TermuxPropertyConstants` classes to define key names so that they can be easily shared between termux and its plugin apps, we provide our own `PreferenceDataStore` for storing key/value pairs. The key name in the XML file can optionally be the same. Check `DebuggingPreferencesFragment` class for a sample. Each new preference category fragment should be added to `app/settings/` with its data store.

This commit may allow support to be added for modifying `termux.properties` file directly from the UI but that requires more work, since writing to property files with comments require in-place modification.

The `Logger` class provides various static functions for logging that should be used from now on instead of directly calling android `Log.*` functions. The log level is automatically loaded from shared preferences at application startup via `TermuxApplication` and set in the static `Logger.CURRENT_LOG_LEVEL` variable. Changing the log level through the settings activity also changes the log level immediately.

The 4 supported log levels are:
- LOG_LEVEL_OFF which will log nothing.
- LOG_LEVEL_NORMAL which will start logging error, warn and info messages and stacktraces.
- LOG_LEVEL_DEBUG which will start logging debug messages.
- LOG_LEVEL_VERBOSE which will start logging verbose messages.

The default log level is `LOG_LEVEL_NORMAL` which will not log debug or verbose messages. Contributors can add useful log entries at those levels where ever they feel is appropriate so that it allows users and devs to more easily help solve issues or find bugs, specially without having to recompile termux after having to manually add general log entries to the source. DO NOT log data that may have private info of users like command arguments at log levels below debug, like `BackgroundJob` was doing previously.

Logging to file support may be added later, will require log file rotation support and storage permissions.
2021-03-13 16:49:29 +05:00
agnostic-apollo
93b506a001 Move Termux Preferences to com.termux.app.settings.preferences package
The termux preferences handling was mixed in with termux properties before an earlier commit. They are now moved out of into a separate sub package, the following classes are added:

- `TermuxPreferenceConstants` class that defines shared constants of the preferences used by Termux app and its plugins. This class should be imported by other termux plugin apps instead of copying and defining their own constants.
- `TermuxSharedPreferences` class that acts as manager for handling termux preferences.
2021-03-12 06:01:31 +05:00
agnostic-apollo
ebf2e472b3 Fix typo in TermuxPropertyConstants 2021-03-12 05:54:12 +05:00
agnostic-apollo
e72841ba27 Update TermuxConstants
The `TermuxConstants` classes has been updated to `v0.3.0`. Check its Changelog sections for info on changes.
2021-03-12 05:53:10 +05:00
agnostic-apollo
5fd91b4f92 Rename com.termux.app.input package to com.termux.app.terminal
Also moves `TermuxViewClient` into com.termux.app.terminal package
2021-03-12 00:07:35 +05:00
agnostic-apollo
10d6eaa5d1 Make TerminalView agnostic of "termux.properties" files.
`TerminalView` will use the `TerminalViewClient` interface implemented by `TermuxViewClient` in termux-app to get "enforce-char-based-input" and "ctrl-space-workaround" property values. It will also not read the file every time it needs to get the property value and will get it from the in-memory cache of `TermuxSharedProperties`.
2021-03-11 21:06:42 +05:00
agnostic-apollo
319446fc15 Disable TerminalView logging. 2021-03-11 20:30:52 +05:00
agnostic-apollo
36be41d0de Remove function that reads the "termux.properties" files from RunCommandService
The `RunCommandService` will now call the `TermuxSharedProperties` for getting current value of `allow-external-apps`, instead of using its own duplicated function to read "termux.properties" files.
2021-03-11 20:30:16 +05:00
agnostic-apollo
ef9e406300 Update TermuxConstants
The `TermuxConstants` and `TermuxPropertyConstants` classes have both been updated to `v0.2.0`. Check their Changelog sections for info on changes.
Some other hardcoded termux paths have been removed as well and are now referenced from `TermuxConstants` class.
2021-03-11 20:11:59 +05:00
agnostic-apollo
7b4acb53c9 Move Termux Properties to com.termux.app.settings.properties package
The termux properties handling was mixed in with termux preferences. They are now moved out of into a separate sub package, the following classes are added:

- `SharedProperties` class which is an implementation similar to android's `SharedPreferences` interface for reading from ".properties" files which also maintains an in-memory cache for the key/value pairs. Two types of in-memory cache maps are maintained, one for the literal `String` values found in the file for the keys and an additional one that stores (near) primitive `Object` values for internal use by the caller. Write support is currently not implemented, but may be added if we provide users a GUI to modify the properties. We cannot just overwrite the ".properties" files, since comments also exits, so in-place editing would be required.
- `SharedPropertiesParser` interface that the caller of `SharedProperties` must implement. It is currently only used to map `String` values to internal `Object` values.
- `TermuxPropertyConstants` class that defines shared constants of the properties used by Termux app and its plugins. This class should be imported by other termux plugin apps instead of copying and defining their own constants.
- `TermuxSharedProperties` class that acts as manager for handling termux properties. It implements the `SharedPropertiesParser` interface and acts as the wrapper for the `SharedProperties` class.
2021-03-11 18:22:11 +05:00
agnostic-apollo
dbf84773d4 Fix potential null exceptions 2021-03-10 02:33:45 +05:00
agnostic-apollo
324a69f7fa Move terminal input related classed to com.termux.app.input package 2021-03-10 02:32:33 +05:00
agnostic-apollo
14c49867f7 Define TermuxConstants class to store all shared constants of Termux app and its plugins
This commit removes almost all hardcoded paths in Termux app and moves the references to the `TermuxConstants` class.

The `TermuxConstants` class should be imported by other termux plugin apps instead of copying and defining their own constants. The 3rd party apps can also import it for interacting with termux apps. App and sub class specific constants are defined in their own nested classes to keep them segregated from each other and for better readability.
2021-03-08 15:09:22 +05:00
agnostic-apollo
395759cc0a Restore keyboard input into terminal view when toggling extra-keys slider input with VOL_UP+q
Check #1420 for details.
2021-03-06 18:27:57 +05:00
agnostic-apollo
ada5087f67 Partial refactor of the mess that is TerminalView
- Decouple the `CursorController`, `TextSelectionCursorController`(previously `SelectionModifierCursorController`) and `TextSelectionHandleView` (previously `HandleView`) from `TerminalView` by moving them to their own class files.
- Fixes #1501 which caused the `java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.` exception to be thrown when long pressing the down key while simultaneously long pressing the terminal view for text selection.
2021-03-06 18:25:10 +05:00
agnostic-apollo
93a5bf8d29 Request android.permission.REQUEST_INSTALL_PACKAGES permission
This will allow users to access `Android/obb` on android 11 after explicitly granting Termux the permission by going to Termux `App Info` in Android `Settings` -> `Advance` -> `Install unknown apps`.

https://medium.com/androiddevelopers/android-11-storage-faq-78cefea52b7c
2021-03-02 16:48:15 +05:00
agnostic-apollo
356a442c6f Request android.permission.DUMP permission
This will allow users to run `dumpsys` command after running `adb shell pm grant com.termux android.permission.DUMP`.

https://developer.android.com/studio/command-line/dumpsys
2021-03-01 12:40:04 +05:00
agnostic-apollo
9fd2cf9834 Fix NoClassDefFoundError exceptions for TermuxActivity
This commit fixes the non-crashing exception `Rejecting re-init on previously-failed class java.lang.Class<androidx.core.view.ViewCompat$2>: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/view/View$OnUnhandledKeyEventListener;` on termux startup due to `setContentView()` call by `TermuxActivity.onCreate()`. The recommended solution seems to be to add `androidx.core:core` dependency, which has solved the issue.

https://issuetracker.google.com/issues/117685087
2021-02-27 13:30:11 +05:00
agnostic-apollo
5a96075025 Update RunCommandService documentation 2021-02-27 13:23:37 +05:00
agnostic-apollo
85b2c44ac7 Fix current working directory default value
This commit fixes the issue when `cwd` is empty and is passed to `Runtime.getRuntime().exec(progArray, env, new File(cwd));`, it raises the `No such file or directory` exceptions when targeting sdk `29`.
2021-02-27 13:22:09 +05:00
agnostic-apollo
108e4cb391 Fix workdir logic in RunCommandService
This commit fixes the workdir logic to not send `EXTRA_CURRENT_WORKING_DIRECTORY` extra to `TermuxService` if workdir is empty, since that will raise `No such file or directory` exceptions if `cwd` is empty when targeting sdk `29`.
2021-02-27 13:14:13 +05:00
agnostic-apollo
80858bab6b Fix path expansion in RunCommandService
This commit fixes `getExpandedTermuxPath()` (previously `parsePath()`) not expanding path if exactly `$PREFIX` is passed and addition of extra trailing slashes in some cases.
2021-02-27 13:10:57 +05:00
agnostic-apollo
00194ebb90 Add logcat errors in RunCommandService
This commit adds `logcat` errors if an invalid intent action is passed or if `allow-external-apps` is not set to `true` while sending an intent to `RunCommandService`, so that users can detect issues.
2021-02-27 13:07:07 +05:00
agnostic-apollo
f50d15d353 Fix "Duplicate finish request for ActivityRecord" errors 2021-02-26 17:09:04 +05:00
Leonid Pliushch
edcebf1336 version 0.108 2021-02-19 01:54:46 +02:00
Leonid Pliushch
fcfd131ccd update bootstrap archives 2021-02-19 01:54:26 +02:00
Leonid Pliushch
ce5e9a9042 Revert "app: enable split apk builds"
Not compatible with F-Droid.

If you can fix F-Droid builds, send pull requests. For now I'm disabling this.

This reverts commit a7eb173178.
2021-02-19 01:45:39 +02:00
Leonid Pliushch
7884cb3bea github actions: test run for publishing maven packages 2021-02-11 23:10:44 +02:00
Leonid Pliushch
d8fcc1f221 github actions: add workflow for publishing terminal-view/terminal-emulator on Github Packages 2021-02-11 23:08:15 +02:00
Leonid Pliushch
397a78f248 publish termux-view and terminal-emulator on Github Packages
Moving from Bintray.
2021-02-11 22:58:56 +02:00
Leonid Pliushch
8baf53b09a update bootstrap archives 2021-02-11 17:51:46 +02:00
Leonid Pliushch
4139bf9424 version 0.107 2021-02-03 23:47:30 +02:00
Leonid Pliushch
94deecb7b1 Merge pull request #1889 from rakslice/numpad_arrows
Implement numpad arrows/home/end/pgup/pgdn/ins/del when num lock is off
2021-02-03 23:43:05 +02:00
Vincent Deville
a4381b7827 ExtraKeysView: SpecialButtons use Button instead of ToggleButton 2021-02-03 23:26:08 +02:00
Vincent Deville
866da75fa9 ExtraKeysView: Use Set instead of List for special buttons keys 2021-02-03 23:26:08 +02:00
Vincent Deville
2b6e9ade07 ExtraKeysView: Handle multiple and popup SpecialButtons 2021-02-03 23:26:08 +02:00
Leonid Pliushch
6d1b0efd3b update bootstrap archives 2021-02-03 23:15:26 +02:00
Leonid Pliushch
e03858f065 export app version in environment variable 2021-02-03 22:39:13 +02:00
Leonid Pliushch
01929397cf TermuxPreferences: get rid of unnecessary switch()'es 2021-02-03 20:27:13 +02:00
Leonid Pliushch
496da3f877 introduce a property for configuring default working directory
Issue https://github.com/termux/termux-app/issues/1192.
2021-02-03 20:22:38 +02:00
Leonid Pliushch
407e4e003a github actions: update actions/upload-artifact to v2 2021-02-03 19:05:19 +02:00
Leonid Pliushch
fec61d315f github actions: debug_build.yml is fixed now? 2021-02-03 18:59:22 +02:00
Leonid Pliushch
f3a3a89f93 github actions: use hardcoded list of debug apks 2021-02-03 18:15:03 +02:00
Leonid Pliushch
5b084f0851 github actions: include split apks to artifact archive 2021-02-03 17:53:08 +02:00
Emil Velikov
a7eb173178 app: enable split apk builds
Currently we build a single APK which handles the four supported ABIs.

Therefore each user, downloads 50-75% more than they need - adding
towards both client/server-side network as well as device storage.

Use a split APK approach - it costs nothing from build and server-side
storage POV.

Note: We're removing ndk:abiFilters - they're incompatible/superseded by
the splits:abi:include list.

Signed-off-by: Emil Velikov <emil.l.velikov@gmail.com>
2021-02-03 17:29:13 +02:00
Leonid Pliushch
fa5117a098 terminal-view: bump library version
Needs to be updated on Bintray: https://bintray.com/termux/maven/terminal-view
2021-02-03 17:02:29 +02:00
Leonid Pliushch
2ef45c85b2 terminal-emulator: bump library version
Needs to be updated on Bintray: https://bintray.com/termux/maven/terminal-emulator
2021-02-03 16:59:34 +02:00
Leonid Pliushch
58b7a26b33 add basic metadata for F-Droid.
Fixes https://github.com/termux/termux-app/issues/622.
2021-02-03 16:43:17 +02:00
Leonid Pliushch
6e2a2ed946 documents provider: fix application icon reference 2021-02-03 15:56:38 +02:00
Leonid Pliushch
7be1fe5555 icon: add regular png icon for compatibility with some launchers
See https://github.com/termux/termux-app/issues/991.

Adaptive icon will continue to be automatically used if supported.
2021-02-03 15:49:03 +02:00
Leonid Pliushch
0e94d52094 readme: remove links to Google Play and Nethunter stores
Both offer outdated builds.
2021-02-03 02:56:53 +02:00
David Kramer
096dedffb1 Remove height offset workarounds / config
* Use WindowInsetsListener to get nav bar height and use that
      instead for calculating adjusted height for extra keys view
2021-02-02 01:31:50 +02:00
David Kramer
b5d491a54c Implement full screen and add FullScreenWorkAround to fix ExtraKeysView
not displaying properly and inconsistently between various devices
2021-02-02 01:31:50 +02:00
rakslice
f6822d6c24 Merge remote-tracking branch 'origin/master' into numpad_arrows
# Conflicts:
#	terminal-view/src/main/java/com/termux/view/TerminalView.java
2021-01-17 14:57:47 -08:00
Leonid Pliushch
32c3ffd57b readme: still need to clarify that app is "not maintained"
Please note that I do not review changes made by project collaborators.
Before submitting changes to branch 'master' ensure they are working
properly. I may not find the issue and fix it in time, like in case
with v0.105 release.
2021-01-17 20:03:05 +02:00
Leonid Pliushch
92570bee06 version 0.106 2021-01-17 19:45:19 +02:00
Leonid Pliushch
05bb399893 terminal-view: fix array indexing 2021-01-17 19:40:29 +02:00
rakslice
fe584940e1 fix TerminalView possiblePropLocations IOBE 2021-01-16 19:54:11 -08:00
rakslice
78cdaef6d2 only send PF1 for num lock when in application mode 2021-01-16 19:53:22 -08:00
rakslice
7b4a69f839 when num lock is off, have numpad keys works as their numlock-off functions (arrows/home/end/pgup/pgdn/insert/delete) 2021-01-16 19:48:55 -08:00
Henrik Grimler
831aa69da8 Merge pull request #726 from dkramer95/OpenBroadcast
Sends broadcast on app open to notify addon termux receivers
2021-01-06 13:48:15 +01:00
Leonid Pliushch
a56cba6843 version 0.105 2021-01-06 14:31:56 +02:00
Leonid Pliushch
9228982632 remove restrictions from viewable file types
Fixes https://github.com/termux/termux-app/issues/1872 and
similar issues.
2021-01-06 14:28:28 +02:00
Leonid Pliushch
38114784f1 update bootstrap archives 2021-01-06 14:23:15 +02:00
Henrik Grimler
b805f1486c Merge pull request #1869 from termux/ctrlspace
Add workaround property to fix ctrl+space on devices where this does not work
2021-01-04 22:41:56 +01:00
Henrik Grimler
7d31b7f480 terminal-emulator: tests: change spaces to tabs for consistency 2021-01-02 09:12:14 +01:00
Henrik Grimler
a0298285e3 terminal-emulator: tests: avoid error about methods not being mocked
unitTests.returnDefaultValues = true fixes it.
More info: http://g.co/androidstudio/not-mocked
2021-01-02 09:12:14 +01:00
Lucy Phipps
538a1d5cdf F-Droid URL for Termux:Styling 2021-01-01 23:48:34 +02:00
Henrik Grimler
f1e973f0d2 terminal-view: add "ctrl-space-workaround" property
Makes it possible to run ctrl+space with hardware keyboards on
devices/ROMs where it otherwise is broken. On devices where it already
works this workaround breaks ctrl+space though.

Where to add this fix was investigated and found by @5bodnar.
2021-01-01 19:20:16 +01:00
Henrik Grimler
b467b68f7b terminal-view: mv code to get properties to its own function 2021-01-01 19:20:11 +01:00
Henrik Grimler
b895cbbb1e app: set importantForAutofill="no" for extra keys input field
Fixes warning "extra_keys_right.xml:2: Missing autofillHints attribute".
2021-01-01 18:13:37 +01:00
Henrik Grimler
fc30eba247 terminal-view: silence warning from use of SHOW_AS_ACTION_ALWAYS
```
Prefer "SHOW_AS_ACTION_IF_ROOM" instead of "SHOW_AS_ACTION_ALWAYS"
```
2021-01-01 18:13:36 +01:00
Henrik Grimler
b1d4c0c7fe app: disable test for "ProtectedPermissions"
Needed for tests to pass after READ_LOGS and WRITE_SECURE_SETTINGS
were added to AndroidManifest.xml.
2021-01-01 18:13:36 +01:00
Henrik Grimler
7fe5bd32c8 Update ndk to r22 2021-01-01 18:12:53 +01:00
Henrik Grimler
43bbef9a11 Update plugins and gradle to latest versions 2021-01-01 18:12:53 +01:00
Henrik Grimler
eaea0f74a5 Fix github links for terminal-{emulator,view} jcenter packages 2021-01-01 18:12:53 +01:00
Leonid Pliushch
cb13a5a531 version 0.104 2020-12-29 13:45:35 +02:00
Kevin LeBlanc
d267843e36 Don't exclude hidden files from document provider (#1220)
Credit to @johnmellor for requesting the document provider in the
first place via #79, mentioning this limitation in a comment on
that issue, and creating a commit like this one to address it.
2020-12-29 13:42:36 +02:00
Leonid Pliushch
5ca67dd885 update bootstrap archives 2020-12-29 13:40:25 +02:00
Leonid Pliushch
6b0d531758 Revert "update readme" 2020-12-17 20:27:46 +02:00
Leonid Pliushch
1b3283bd69 update bootstrap archives 2020-12-11 20:07:40 +02:00
Leonid Pliushch
2820f6a7b8 update readme 2020-12-11 17:36:14 +02:00
Leonid Pliushch
8925ae67bb update readme 2020-12-11 17:35:28 +02:00
Lucy Phipps
4066c5df42 don't add $PREFIX/bin/applets to $PATH
busybox doesn't use that folder anymore, and is deprecated anyway
2020-11-24 23:16:17 +02:00
Leonid Pliushch
20aac6aa72 copy FUNDING.yml from termux-packages repo 2020-11-18 03:04:38 +02:00
Leonid Pliushch
e1f799f9a1 version 0.103 2020-11-17 20:40:40 +02:00
Leonid Pliushch
4479de4b9b update bootstrap archives 2020-11-17 20:35:55 +02:00
Leonid Pliushch
3a8f53a54c add permission WRITE_SECURE_SETTINGS
https://github.com/termux/termux-api/issues/211
2020-11-17 20:07:33 +02:00
Leonid Pliushch
2f04a6186b add READ_LOGS permission
* https://github.com/termux/termux-app/issues/873
* https://github.com/termux/termux-app/issues/1821
2020-11-17 20:03:10 +02:00
Leonid Pliushch
ad64dd7c3d update bootstrap archives 2020-10-12 20:39:28 +03:00
Fredrik Fornwall
dfc4595ec5 version 0.102 2020-10-02 10:30:18 +02:00
Fabian Henneke
8c80efb904 Add an "Autofill password" context menu action 2020-10-02 10:21:34 +02:00
Leonid Pliushch
564079c7e9 update .gitattributes 2020-10-01 01:08:39 +03:00
Leonid Pliushch
5b6fd9b88c version 0.101 2020-09-28 03:30:21 +03:00
Leonid Pliushch
63bfe95848 update bootstrap archives again
Fixes faulty dpkg https://github.com/termux/termux-packages/issues/5858.
2020-09-28 03:28:49 +03:00
Leonid Pliushch
af95a99854 version 0.100 2020-09-28 00:59:52 +03:00
Leonid Pliushch
25523ae224 fix bootstrap archive checksum for arm
Due to leading zero issues, SHA-256 checksum for ARM variant will
have 63 characters instead of correct 64...
2020-09-28 00:52:44 +03:00
Leonid Pliushch
665adb6895 update bootstrap archives 2020-09-28 00:46:38 +03:00
Henrik Grimler
eb7fda28df Merge pull request #1764 from agnostic-apollo/termux-run-command-crash-and-foreground-patch
Fix RunCommandService crash and foreground issue
2020-09-27 21:02:56 +02:00
agnostic-apollo
7063a3a9da Add @override annotation to onStartCommand() function of RunCommandService. 2020-09-18 22:25:05 +05:00
agnostic-apollo
6e02b99ff6 Update RunCommandService docs. 2020-09-18 22:22:35 +05:00
agnostic-apollo
9aae665bfc Fix issue where termux session does not come to foreground automatically in android >= 10 unless user manually clicks termux notification for "RUN_COMMAND" intents and "Termux:Tasker" plugin actions that have background mode "false" because of new restrictions of starting activities from background. This is done by adding "android.permission.SYSTEM_ALERT_WINDOW" permission in AndroidManifest.xml so that the user may optionally grant "Draw Over Apps" permission to termux to fix the issue. 2020-09-18 22:21:58 +05:00
agnostic-apollo
52ce6cc94b Add support for "$PREFIX/" and "~/" prefix in "RUN_COMMAND" intent extras for paths. 2020-09-18 22:21:31 +05:00
agnostic-apollo
f91168eff4 Fix issue where termux crashes occasionally in android >= 8 because "startForeground()" function is not being called before running "startForegroundService()" in RunCommandService. 2020-09-18 22:20:23 +05:00
Henrik Grimler
8faa5b2151 TerminalEmulator: fix bug in DECRQM handling
Reported in https://github.com/termux/termux-app/issues/1752
2020-09-12 21:27:24 +02:00
Leonid Pliushch
216cc10f3c Revert "add gradle distribution sha256"
Was initially added to try fix F-Droid builds but appears missing
distribution sha256 is not a cause of the issue.

F-Droid maintains own set of Gradle SHA-256 checksums.

This reverts commit cba80b6c0b.
2020-09-12 19:06:54 +00:00
Leonid Pliushch
cba80b6c0b add gradle distribution sha256 2020-09-01 03:38:53 +03:00
Leonid Pliushch
850faa25dd version 0.99 2020-08-26 20:51:05 +03:00
Leonid Pliushch
a108b2bd6b gradle 6.6.1 2020-08-26 20:40:29 +03:00
Leonid Pliushch
b95823d7a8 update bootstrap archives 2020-08-26 20:27:47 +03:00
Leonid Pliushch
382da7e8f7 terminal view: provide a workaroud for issue with some keyboards
Issue https://github.com/termux/termux-app/issues/686.

Note that there can be a better workaround which I don't know...
2020-08-26 20:04:22 +03:00
blackcat-917
ba9c118b50 readme: fix a copy-paste typo (#1720) 2020-08-22 00:17:45 +03:00
Leonid Pliushch
531c32f3c9 CI: do builds for android-10 branch 2020-08-16 22:56:55 +03:00
Leonid Pliushch
db2f50c76e extra keys: use TextUtils.join instead of String.join
String.join() is available only on Android API 26+ but our current
minimal is 24.

See https://github.com/termux/termux-app/issues/1670.
2020-08-14 15:08:32 +03:00
Leonid Pliushch
784affe39c linter: fix wakelock log tag
See https://github.com/termux/termux-app/issues/1670.
2020-08-14 15:03:51 +03:00
Leonid Pliushch
b486d29d23 fix RUN_COMMAND permission description
See https://github.com/termux/termux-app/issues/1713.
2020-08-14 14:50:34 +03:00
Fredrik Fornwall
332f1104a3 Update drawerlayout library 2020-08-09 00:07:35 +02:00
Leonid Pliushch
5a70be1523 terminal emulator: flush remaining process output data when terminating session 2020-08-08 02:22:47 +03:00
Leonid Pliushch
619552ec5c version 0.98 (v0.97 is non-release, so updating from 0.96 to 0.98) 2020-08-07 15:39:44 +03:00
Leonid Pliushch
70580abd50 update bootstrap archives 2020-08-07 15:39:11 +03:00
Henrik Grimler
f191c35851 Merge pull request #1693 from landfillbaby/patch-1
update WcWidth.java, add note about c version
2020-08-07 13:39:33 +02:00
lucy phipps
bd7ed28981 update WcWidth.java, add note about c version 2020-08-07 07:38:06 +01:00
hannesa2
b68bd107c1 Gradle 6.5.1 (#1686) 2020-08-04 15:37:18 +03:00
hannesa2
5075273362 Android Studio 4.0.1 (#1687) 2020-08-04 15:36:41 +03:00
cn
04268f4c20 move sdk version configs to gradle.properties (#1685) 2020-08-04 15:36:14 +03:00
Leonid Pliushch
6f24628fd2 version 0.96 2020-07-30 23:56:27 +03:00
Leonid Pliushch
debbe44809 update bootstrap archives 2020-07-30 23:47:26 +03:00
Agnostic Apollo
b2ff0e4051 Changed static string "EXTRA_EXECUTE_IN_BACKGROUND" access to public 2020-07-30 21:39:41 +03:00
Agnostic Apollo
9e7029b76a Receive "RUN_COMMAND_ARGUMENTS" extra for "RUN_COMMAND_ACTION" intent
as a string array extra instead of a string extra since TermuxService expects it that way.

Added "RUN_COMMAND_BACKGROUND" boolean extra so that Termux session can be started in background
when running a command.

Updated usage docs.

Check #1029 for details.
2020-07-30 21:39:41 +03:00
Egor Zhdanov
51370799c7 update notification icon 2020-07-14 12:36:56 +03:00
Danny Lin
0910844896 Invert selected text instead of highlighting
Highlighting text in the terminal often makes it hard to read, which
can be problematic for users who want to adjust or review selections
before copying them. For example, the default theme makes white and
green text hard to read on its light gray selection background, and
there are plenty of other themes where the choice of text and cursor
colors would hinder selection readability.

To fix this issue and make selected text more legible in nearly all
combinations of colors, invert selected text instead of highlighting it.
This is more common among terminal emulators anyway:
    Invert:    xterm, fbcon, kitty, Konsole, Alacritty, Tilix,
               gnome-terminal (7)
    Highlight: Termux, Terminal.app, iTerm2, Windows Terminal (4)
2020-07-12 17:54:47 +03:00
Danny Lin
f33ebf810f Fix selection rendering with alternate cursor styles
There is currently a bug where selection rendering is broken if the
active cursor shape is anything other than the default solid box.
Selected text is normally highlighted by effectively rendering a cursor
over all of the characters in the selection region, but if the cursor is
a bar, the resulting selection highlight is too narrow to cover the full
width of the selection. Similarly, if the cursor is an underline, all of
the selected text will be underlined instead of highlighted.

To fix this issue, treat selections different from cursors in the
rendering logic and force the renderer to always use the block cursor
style for rendering selections. That way, we get correct behavior
regardless of what the current cursor shape is.
2020-07-04 15:31:45 +03:00
Felix C. Stegerman
930029b5d2 export COLORTERM=truecolor 2020-06-22 02:07:20 +03:00
Leonid Pliushch
33def928cf add DEX2OATBOOTCLASSPATH environment variable 2020-06-19 22:25:51 +03:00
Leonid Pliushch
fc04a93990 get rid of Android 5 legacy stuff
We don't need LD_LIBRARY_PATH as of Android 7.0.
2020-06-19 22:18:28 +03:00
Leonid Pliushch
8d302aa9fe fix am on Android R 2020-06-19 22:18:21 +03:00
Leonid Pliushch
2224d917a3 update ndk version 2020-06-17 14:08:58 +03:00
Leonid Pliushch
664ec43f94 version 0.95 2020-06-09 23:08:23 +03:00
Leonid Pliushch
6cf742460c extra keys: make popup & pressed button no longer transparent
Fixes visual issue when popup overlaps text of upper row.
2020-06-09 22:54:57 +03:00
Leonid Pliushch
72981fb981 Use vector drawables for text selection handle
https://github.com/termux/termux-app/issues/1036
2020-06-09 21:47:11 +03:00
Leonid Pliushch
2c5534e2c1 RunCommandService: update information about usage 2020-06-09 16:07:42 +03:00
Leonid Pliushch
5b32540635 minor refactoring: RunCommand => RunCommandService 2020-06-09 15:48:56 +03:00
Leonid Pliushch
db3ff7b24a Provide a service for executing commands by third-party applications
Re-implementation of https://github.com/termux/termux-app/pull/1029.

If Termux has property "allow-external-apps" set to "true", a third-party
program will be able to send intents for executing custom commands
within Termux environment.

Third-party program must declare permission "com.termux.permission.RUN_COMMAND".
2020-06-09 15:36:08 +03:00
Trygve Aaberge
5f71e3e73a Join lines that fills the width when selecting urls
Some terminal applications, like mutt and weechat, prints a newline at
the end of each line even if text is wrapped. This causes urls which are
wrapped to not be selectable in full.

By ignoring newlines when the text fills the entire width of the screen,
those urls can be selected. Many other terminal emulators do this as
well.

A drawback of this is that if a url happens to fill the width of the
screen, the url selection will include the first word of the next line,
but this doesn't happen that often so I think it's an okay tradeoff.

Fixes #313
2020-06-09 13:41:36 +03:00
Josh Home
3e04ea4cb0 Add Log file to view intent #1497 2020-06-09 12:31:59 +03:00
Trygve Aaberge
0af823607a Improvements to extra keys (#1479)
* Make popup keys for extra keys row configurable

This makes the keys you get when swiping up on a key configurable. You
can configure such a key by using an array of strings instead of a
single string in the row. The first entry will be the normal key and the
second will be the extra key.

This is a slightly breaking change, as people that have configured
custom extra keys with "-" or "/" will have to change the config to keep
the popup keys. The default config will remain the same in terms of
functionality, i.e. it includes the same popup key for "-".

* Make popup keys interact well with long press keys

This stops the repeat action when the popup is shown, and makes sure the
popup is closed when you release even if there has been some repeat
actions.

* Support configuring the style of the extra keys

This adds a setting for choosing between the different ways to render
key names that were already present in ExtraKeysView.

The available setting values are "arrows-only", "arrows-all", "all",
"none" and "default". Other values will fallback to "default".

Can be used as a workaround for #1410

* Support using modifier keys with letter keys in extra keys

This allows you to use the modifier keys on the extra keys rows, e.g.
ctrl, together with another button on the extra keys rows, as long as
that button is a normal letter and not a special key. Support for
special keys will come in the next commit.

* Support using modifier keys with special keys in extra keys

This allows you to use the modifier keys on the extra keys rows together
with a special key on the extra keys rows, e.g. CTRL+LEFT.

Fixes #745, fixes most of #895 and possibly #154

* Support mapping extra keys to other actions

This adds a setting called extra-keys-map which allows you to map a key
on the extra keys rows to another action. The value is a json object
where the key is the button text as configured in extra-keys and the
value is the action. Multiple actions can be used, but if they are
special characters (like ESC or LEFT) they have to be separated from the
other characters with a space on each side. If you want an actual space
character, use SPACE.

For example if you want to add a key to go to the next active channel in
weechat, you can use this:
    extra-keys-map = {"weechat next": "ESC a"}
And then add "weechat next" to extra-keys. The name can of course be
whatever you want.

Or if you want the button for the UP arrow to show ⇧ instead of ↑, you
can use this:
    extra-keys-map = {"⇧": "UP"}
And put "⇧" in extra-keys instead of "UP".

Modifier keys (ctrl, alt and shift) can't be used in this map yet.
Support for ctrl and alt will come in the next commit.

I think this fixes #1186

* Support CTRL and ALT in extra keys map

This allows you to use CTRL and ALT in extra-keys-map.

For example if you want a button to exit the terminal, you can use this:

    extra-keys-map = {"exit": "CTRL d"}

And add "exit" to extra-keys.

* Support a KEYBOARD button in extra keys

This toggles showing the keyboard input method.

* Support specifying macro keys in the extra-keys option

Instead of specifying macros in the separate extra-keys-map option by
matching the key name in the two options, you can now use "macro"
instead of "key" in extra-keys, and it will be a macro, i.e. a sequence
of multiple keys separated by space.

* Remove option extra-keys-map

Now that you can specify macro in extra-keys, there is no point in
having this separate option. Instead of specifying the value to display
as key, and the macro to perform in extra-keys-map, you would now
specify the value to display in the display property and the macro to
perform in the macro property.

* Lookup display text when creating ExtraKeyButton

This will make it easier to support key aliases for macros in the next
commit.

* Add support for a key to open the drawer

Fixes (I think) #1325
2020-06-09 12:17:07 +03:00
Trygve Aaberge
4d9c0c315e Update notification icon to match launcher icon
This is the launcher icon with a circle around it. I added the circle
because the icon has a transparent background, so it looks a bit weird
with just the >_.
2020-06-09 12:11:49 +03:00
Leonid Pliushch
9c32935ca2 fix ndk version for terminal-emulator module 2020-06-09 12:08:13 +03:00
Leonid Pliushch
669c336e2f update ndk version 2020-06-09 12:02:41 +03:00
Trygve Aaberge
35842cf4a6 Set orientation of HandleView in show (#1477)
* Place long press menu above selection

Previously, the long press menu would cover the first line of the
selection.

* Flip selection handle at different positions depending on drag direction

When the selection handle changes direction, the selection jumps to the
new point of the handle. When the handle changes direction at the same
place when you come from the left as from the right, that makes it
impossible to select the characters which are at the position where it
changes direction.

With this change the handle remains pointing towards the edge further
into the line when you drag it from the edge and against the center.

* Set orientation of HandleView when showing it

When you hold down on a word that starts or ends at the edge of the
screen, the handle will appear outside of the screen. This happens
because the orientation was only switched when the handle is dragged, so
when it is shown it just used the same orientation as it had for the
last selection.

Relates to #334, but not sure if it fixes it completely.
2020-06-09 11:59:59 +03:00
Trygve Aaberge
b086270a5a Support auto detection of dark theme
By default it uses the system setting. If use-black-ui is set to either
true or false, that overrides it.

Fixes #1351 properly, fixes #1354
2020-06-09 11:59:07 +03:00
Leonid Pliushch
f39f06a540 update bootstraps again
* Reduced archive size by using nano as preinstalled text editor instead of vim.
* Updated command-not-found and termux-tools.
2020-06-07 22:36:14 +03:00
Leonid Pliushch
58440bc88d update bootstraps again
Added few more packages to bootstraps which are replacements to
some busybox applets.
2020-06-07 03:56:51 +03:00
Leonid Pliushch
9f438e2912 update bootstraps 2020-06-06 22:42:29 +03:00
Leonid Pliushch
f794bfcadc Handle all exceptions when loading termux.properties (#1590)
* Catch all exceptions which can occur.
* Print short description in toast message about occurred exception.
2020-06-06 21:20:05 +03:00
hannesa2
b6d7831646 Fix CI build and see result in pull request (#1565) 2020-05-24 02:05:24 +02:00
Leonid Pliushch
d212198e30 update bootstraps 2020-05-24 02:44:06 +03:00
Fredrik Fornwall
c2843897ac Fix build by specifying ndkVersion 2020-05-24 01:27:01 +02:00
Fredrik Fornwall
c4c4912a7e Avoid specifying ndk version 2020-05-24 01:22:33 +02:00
Hannes Achleitner
38a3319ca2 show result in pull request 2020-05-24 01:08:01 +02:00
Hannes Achleitner
7e13b8aa2e Verify Gradle 2020-05-24 01:08:01 +02:00
Frieder Bluemle
6dca19ae00 Update Android Gradle plugin to 3.6.3 2020-05-24 01:06:15 +02:00
Leonid Plyushch
2659c06c5d Remove irrelevant message from bootstrap installation error
Bootstraps are no longer downloaded from the Internet.
2020-04-30 14:34:35 +03:00
Fredrik Fornwall
9b7c7102b2 Bump version to 0.94
Some checks failed
Build / build (push) Has been cancelled
Unit tests / testing (push) Has been cancelled
2020-03-24 23:25:49 +01:00
Daniel Lublin
1819087ca0 Add tests for URL fragment 2020-03-24 23:13:14 +01:00
Daniel Lublin
366a61f052 Grab fragment when extracting URLs for selection 2020-03-24 23:13:14 +01:00
Leonid Plyushch
6e224cabcf set ndk version in build.gradle
Fixing CI builds.
2020-03-21 11:31:52 +02:00
Leonid Plyushch
8c8fa96133 update bootstraps 2020-03-21 10:35:17 +02:00
Fredrik Fornwall
537f2ed97e Update gradle wrapper 2020-03-20 14:42:43 +01:00
Fredrik Fornwall
0e23315c41 Add android.useAndroidX=true to gradle.properties 2020-03-20 10:44:24 +01:00
Fredrik Fornwall
2cde986419 Update Android gradle plug-in 2020-03-16 13:58:34 +01:00
Fredrik Fornwall
d28939810c Update Robolectric from 4.3 to 4.3.1 2020-02-21 14:02:53 +01:00
Fredrik Fornwall
93724b7aa6 Bump version to 0.92
Some checks failed
Build / build (push) Has been cancelled
Unit tests / testing (push) Has been cancelled
2020-02-09 22:59:33 +01:00
x0b
5d06f040e8 Fix deletable flags in DocumentProvider
A file is deletable if the _parent_ is writable, not the file itself.
2020-02-09 22:57:27 +01:00
x0b
ed9afa082a Fix #1350: Support createDocument(...) 2020-02-09 22:57:27 +01:00
x0b
9703bd31ad Fix #1424: Support ACTION_OPEN_DOCUMENT_TREE 2020-02-09 22:57:27 +01:00
Leonid Plyushch
9749f25eba update bootstrap archives
v19 had a problem with metadata which broke the package manager.
2020-02-09 12:34:11 +02:00
Leonid Plyushch
453b838b24 update bootstrap archives 2020-02-08 20:33:34 +02:00
Leonid Plyushch
9fdf2a49fd update readme 2020-02-07 15:48:32 +02:00
Leonid Plyushch
3270506bff update issue templates 2020-02-07 15:05:16 +02:00
Leonid Plyushch
a240f4cf45 CI: fix job name in run_tests.yml 2020-02-07 14:02:34 +02:00
Leonid Plyushch
4647beb0d2 update readme 2020-02-07 13:57:08 +02:00
Leonid Plyushch
d92e806461 CI: update workflow labels 2020-02-07 13:55:45 +02:00
Leonid Plyushch
b75cf0bb84 CI: add configuration for running terminal emulator unit tests 2020-02-07 13:50:12 +02:00
Leonid Plyushch
f928efed4e CI: switch to Github Actions 2020-02-07 13:47:22 +02:00
Fredrik Fornwall
36db64d585 Bump version to 0.90 2020-02-02 03:36:52 +01:00
Jean Schurger
b8f0430699 Prevent kill by Samsung Dex on plug/unplug. 2020-02-02 03:36:13 +01:00
Edontin
90e6260d5e Allow the user to disable virtual key emulation.
Use volume-keys=volume within termux.properties to disable.
2020-01-06 10:21:32 +01:00
Fredrik Fornwall
566d656c16 Avoid trailing slash in CWD (fixes #1413) 2020-01-05 19:14:52 +01:00
Fredrik Fornwall
b729085d52 Bump version to 0.88 2020-01-05 19:14:35 +01:00
Fredrik Fornwall
a94bcb02f1 Bump version to 0.86 2020-01-05 02:02:05 +01:00
Fredrik Fornwall
e28be01dc2 Create new terminal sessions with directory of active session
This mimics the behaviour of most tabbed terminal emulators.

Fixes #1009.
2020-01-05 02:00:25 +01:00
Archenoth
7f7c1efac1 Add PendingIntent in TermuxService to return data from execution
This commit adds an optional final argument to the BackgroundJob
constructor for a PendingIntent to return the results of its
execution to, and also attempts to pass an optional pendingIntent to
it from the Service start intent
2020-01-05 01:07:11 +01:00
Archenoth
76df44e6bb Start stderr BackgroundJob Thread 2020-01-05 01:07:11 +01:00
Fredrik Fornwall
fd13f3f98d Handle magnet links with termux-url-opener
Fixes #1339 and #1382.
2020-01-05 01:02:47 +01:00
Leonid Plyushch
269c3cafb0 properly set bootclasspath environment variable 2020-01-05 00:38:49 +01:00
Fredrik Fornwall
662b5cace6 Update gradle wrapper 2020-01-01 22:57:52 +01:00
Fredrik Fornwall
3c01b09fff Bump version to 0.85 2019-12-29 18:53:00 +01:00
Fredrik Fornwall
1ce2db9929 Merge branch 'maoabc-master' 2019-12-29 18:50:34 +01:00
Fredrik Fornwall
c987ff718a Update Android Gradle plug-in 2019-12-29 18:49:41 +01:00
Leonid Plyushch
9e295b105c terminal emulator: clear scrollback buffer when resetting to initial state
Fixes utility 'reset' not being able to clear scrollback buffer.
2019-12-29 18:49:41 +01:00
Fredrik Fornwall
37b3a9f8db Update Android Gradle plug-in 2019-12-29 18:49:10 +01:00
Leonid Plyushch
490853e427 terminal emulator: clear scrollback buffer when resetting to initial state
Fixes utility 'reset' not being able to clear scrollback buffer.
2019-12-24 00:32:40 +01:00
mao
9ebedc4740 Merge remote-tracking branch 'remotes/origin/master' 2019-12-19 00:43:45 +08:00
mao
82730d9e08 confict 2019-12-19 00:34:14 +08:00
mao
3d2756f376 Optimize handleView move. 2019-12-18 23:50:23 +08:00
Leonid Plyushch
6db51bdec0 remove SDK_INT conditionals
We are targting on API 24, so conditionals for Android 5/6 compatibillity
are useless now.
2019-12-18 14:06:06 +02:00
mao
c238c8bddb Optimize handle view 2019-12-18 14:00:58 +02:00
mao
60e1556871 Stop selection mode on enter 2019-12-18 14:00:57 +02:00
mao
e47bd31681 Selection mode fling 2019-12-18 14:00:57 +02:00
mao
951df6b4e2 Add selection mode cursor controller 2019-12-18 14:00:32 +02:00
Leonid Plyushch
fcd3bc1133 update gradle wrapper 2019-12-02 17:19:17 +02:00
Fredrik Fornwall
f4db9ff212 Bump version to 0.84 2019-11-24 19:10:10 +01:00
Leonid Plyushch
87841886d4 extra keys: improve handling of DnD mode
Do not disturb mode is now handled only for SDKs pre-28. Extra keys will
not vibrate only when total silence mode is used.
2019-11-24 19:08:32 +01:00
Leonid Plyushch
cf883f5f05 extra keys: handle actions UP & CANCEL separately
Related issue: https://github.com/termux/termux-app/issues/905
2019-11-24 19:08:32 +01:00
Fredrik Fornwall
677d75e173 Remove outdated tests
Remove tests that asserted that Cursor Down (CUD) and Cursor Up (CUU)
escape sequences were affected by the scrolling region set by DECSTBM.

This was incorrect and recently fixed:
https://github.com/termux/termux-app/issues/1340
2019-11-24 19:07:34 +01:00
Fredrik Fornwall
cdccc2c433 Do not limit cursor movement to scroll region
The scrolling region set by DECSTBM should not affect the
Cursor Down (CUD) and Cursor Up (CUU) escape sequences.

Fixes #1340.
2019-11-10 22:05:06 +01:00
Fredrik Fornwall
070436a6ed Bump version to 0.82 2019-11-10 19:26:10 +01:00
Fredrik Fornwall
69ded4b33e Update Android gradle plug-in 2019-11-10 19:26:01 +01:00
Fredrik Fornwall
e5a8c0eb4a Cleanup after requiring Android 7.0 2019-11-03 22:39:21 +01:00
Fredrik Fornwall
29815fb3f0 Fix gradle deprecation warning 2019-11-03 22:35:52 +01:00
Fredrik Fornwall
e930ac08aa Bump version to 0.78 2019-11-03 19:18:31 +01:00
Fredrik Fornwall
3b969a0077 Bump version to 0.77 2019-10-28 01:54:50 +01:00
Leonid Plyushch
ae717d8f5f do not allow to backup data by Google
Why:

 * During backup process Termux is being killed in most cases.
 * Backup data is limited to 25 MB.
 * Backup may not be performed/updated in certain cases.
2019-10-27 20:33:12 +01:00
Leonid Plyushch
6c55c3c1be bootstraps: update to v18 2019-10-20 22:06:18 +03:00
Fredrik Fornwall
c0a0822843 Do not set LD_LIBRARY_PATH by default
See https://github.com/termux/termux-app/issues/1286
2019-10-20 20:48:10 +02:00
Fredrik Fornwall
08b08d1c47 Bump version to 0.76 2019-10-20 20:16:48 +02:00
Fredrik Fornwall
c76cec186a Include bootstrap zips as shared libraries 2019-10-20 20:15:50 +02:00
Leon Omelan
5ba3f7cf6d Made Black UI an option to configure 2019-10-20 20:05:48 +02:00
Leon Omelan
468f878a38 Unified UI colors across the app. Dark sidebar and dark app theme for
dark Alert Dialogs
2019-10-20 20:05:48 +02:00
Fredrik Fornwall
c50a367063 Add .cxx folder to gitignore 2019-10-13 20:48:24 +02:00
Leonid Plyushch
4189f598b9 add permission ACCESS_NETWORK_STATE
Seems to be required by some Android TV devices.
2019-10-13 20:05:55 +02:00
mao
fdb3764f5c Optimize handle view 2019-10-11 07:37:57 +08:00
Leonid Plyushch
cb67e805de update readme 2019-10-07 15:32:21 +03:00
Leonid Plyushch
3f83368f42 CI: update .cirrus.yml 2019-10-07 15:01:22 +03:00
Leonid Plyushch
8c7462678f update readme 2019-10-07 00:21:57 +03:00
Leonid Plyushch
6f11390cc4 update readme 2019-10-07 00:20:33 +03:00
Leonid Plyushch
33d390a228 CI: development builds are now signed with publically-shared key
This commit adds keystore which is shared with community. People freely can
use it for creating own Termux app and plugin builds. However, builds signed
with such keystore cannot be proven as official.

Signed-off-by: Leonid Plyushch <leonid.plyushch@gmail.com>
2019-10-06 23:24:15 +03:00
mao
937eb350b2 Stop selection mode on enter 2019-10-05 18:54:15 +08:00
mao
3b4ece6bd8 Selection mode fling 2019-10-05 18:30:54 +08:00
mao
35a4fdacbe Add selection mode cursor controller 2019-10-05 18:05:42 +08:00
Leonid Plyushch
0332779d6a update readme 2019-10-04 23:50:46 +03:00
Leonid Plyushch
f19575b942 readme: update status badge url 2019-10-04 02:16:22 +03:00
Leonid Plyushch
6a95809da3 CI: add test keystore 2019-10-04 02:08:44 +03:00
Leonid Plyushch
ac8dab70ef CI: add initial configuration for Cirrus 2019-10-04 01:59:49 +03:00
Leonid Plyushch
243285f7c2 CI: remove Travis setup 2019-10-04 01:59:49 +03:00
Fredrik Fornwall
b6182216d5 Update android gradle plug-in 2019-10-02 21:37:23 +02:00
Fredrik Fornwall
e206121bbc Bump version to 0.75 2019-09-08 21:43:52 +02:00
Fredrik Fornwall
deceffad00 Update gradle configuration 2019-09-08 21:43:07 +02:00
Leonid Plyushch
c19909cef1 improve url matching regex
Now it support complex URLs and some other schemes beyond just http/ftp.
2019-09-08 20:10:26 +02:00
Leonid Plyushch
5b7e40638c handle possible ActivityNotFoundException when requesting to disable battery optimizations
Related issue: https://github.com/termux/termux-app/issues/1224
2019-09-08 20:10:15 +02:00
Leonid Plyushch
a3673d1af5 extra keys: follow DnD mode and haptic feedback Android settings
Do not vibrate when:

 * Do not disturb mode is on.

 * Haptic feedback is disabled in Android settings.
2019-09-08 20:08:16 +02:00
Fredrik Fornwall
d5f9cf85c9 Bump version to 0.74 2019-08-18 22:11:21 +02:00
hungphd
549f09573f Change method name 'with' to 'getInstance' 2019-08-18 20:30:34 +02:00
Daniil Gentili
94e5bc86fb Ignore battery optimization settings to prevent suspension 2019-08-18 20:23:00 +02:00
Fredrik Fornwall
370ac2bd89 Avoid joining lines for shared transcript (#1166) 2019-08-04 19:42:36 +02:00
Fredrik Fornwall
7041f41981 Bump version to 0.73 2019-08-04 17:03:41 +02:00
Fredrik Fornwall
dd6a21257b Update the Android Gradle plugin 2019-08-04 16:44:58 +02:00
Fredrik Fornwall
445da0c4ea Fix problem with sharing large terminal transcript
Fixes #1166.
2019-07-14 20:26:16 +02:00
Sacha Chua
92980824b1 Add function keys F1 to F12 as keyboard options 2019-07-04 23:27:13 +02:00
Michal Bednarski
7d42b07a32 Also export ANDROID_TZDATA_ROOT
Since Android Q Beta 2 ANDROID_TZDATA_ROOT is required
in addition to ANDROID_RUNTIME_ROOT in order to run
`am` and `dalvikvm`
2019-07-04 23:26:55 +02:00
Fredrik Fornwall
e502b9169f Respect failsafe sessions in ACTION_EXECUTE 2019-06-09 20:00:17 +02:00
Leonid Plyushch
9f2d887ca0 minor fix for log message while removing tmpdir 2019-06-09 14:31:09 +03:00
Fredrik Fornwall
e6aacc5f08 Update Android Gradle plug-in 2019-05-27 14:07:50 +02:00
Fredrik Fornwall
ff935be54a Add versionName gradle task 2019-05-27 00:07:41 +02:00
Fredrik Fornwall
4e76162346 Bump version to 0.72 2019-05-27 00:03:42 +02:00
Leonid Plyushch
5fa4f2647b set BOOTCLASSPATH environment variable
Required on at least Android 5. Otherwise utilities that use dalvikvm may fail.

Issue happens on Nexus 7 with official 5.0/5.1 firmware and possibly other
AOSP ROMs.
2019-05-26 20:05:03 +02:00
Fredrik Fornwall
81b5889a26 Bump version to 0.71 2019-05-21 21:52:39 +02:00
Leonid Plyushch
514f59258a let $PREFIX/tmp be cleaned when TermuxService is going to be stopped 2019-05-21 21:52:14 +02:00
Leonid Plyushch
9f79393aa5 Revert "clean /tmp directory on cold start"
This reverts commit beb8a004e6.
2019-05-21 21:52:14 +02:00
Leonid Plyushch
e49d514236 Revert "sessions: do not clear TMPDIR if application was not started"
This reverts commit bd45837d93.
2019-05-21 21:52:14 +02:00
Leonid Plyushch
4e1462326c remove intent category "default"
It was needed only when Termux had separate icon for failsafe activity.
2019-05-21 21:52:14 +02:00
Fredrik Fornwall
b0f1773a92 Bump version to 0.70 2019-05-20 13:32:35 +02:00
Fredrik Fornwall
af9f28c010 Remove the failsafe activity
The failsafe activity were infrequently used while confusing users.

Replace it with an app shortcut on Android 7.1+ and a separate app on
earlier versions.
2019-05-20 13:32:03 +02:00
Nishith Khanna
fef0c66868 Tweak notification icon color to make it work with dark notifcations
As the notification icon and text is set to black, ROMs like Samsung OneUI and themes that change notifications to have a dark background will have visibility issues with Termux notifications. This commit sets a neutral color which will be visible on both white and dark backgrounds.
2019-05-20 13:27:58 +02:00
Fredrik Fornwall
7a5da83ce2 Bump version to 0.69 2019-05-12 00:18:02 +02:00
Fredrik Fornwall
97f01387b9 Update Android Gradle plugin 2019-05-12 00:17:16 +02:00
Jonas L
012ff83a06 Add limit for the bell (vibration)
This fixes https://github.com/termux/termux-app/issues/442
2019-05-12 00:13:54 +02:00
Michal Bednarski
a082c63849 Make am work on Android Q 2019-05-12 00:10:27 +02:00
Fredrik Fornwall
8c27084086 Bump version to 0.68 2019-04-22 00:54:27 +02:00
Fredrik Fornwall
f4fb45cbd6 Add the label attribute on TermuxActivity
Should fix the F-Droid naming of the app, see
https://github.com/termux/termux-app/issues/1107
2019-04-21 20:19:34 +02:00
Leonid Plyushch
0605fc4843 reformat res/values/strings.xml 2019-04-18 20:22:40 +03:00
Leonid Plyushch
3bbd61f9d7 extra keys view: fix crash in some cases
AndroidRuntime: FATAL EXCEPTION: main
AndroidRuntime: Process: com.termux, PID: 15799
AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.widget.ToggleButton.isPressed()' on a null object reference
AndroidRuntime:        at com.termux.app.ExtraKeysView.readSpecialButton(ExtraKeysView.java:119)
AndroidRuntime:        at com.termux.app.TermuxViewClient.readControlKey(TermuxViewClient.java:116)
AndroidRuntime:        at com.termux.view.TerminalView.inputCodePoint(TerminalView.java:655)
AndroidRuntime:        at com.termux.view.TerminalView$2.sendTextToTerminal(TerminalView.java:336)
AndroidRuntime:        at com.termux.view.TerminalView$2.commitText(TerminalView.java:273)
AndroidRuntime:        at com.android.internal.view.IInputConnectionWrapper.executeMessage(IInputConnectionWrapper.java:341)
AndroidRuntime:        at com.android.internal.view.IInputConnectionWrapper$MyHandler.handleMessage(IInputConnectionWrapper.java:85)
AndroidRuntime:        at android.os.Handler.dispatchMessage(Handler.java:105)
AndroidRuntime:        at android.os.Looper.loop(Looper.java:164)
AndroidRuntime:        at android.app.ActivityThread.main(ActivityThread.java:6944)
AndroidRuntime:        at java.lang.reflect.Method.invoke(Native Method)
AndroidRuntime:        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
AndroidRuntime:        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
2019-04-17 03:13:32 +03:00
Fredrik Fornwall
42fc0ea1cd Bump version to 0.67 2019-04-14 20:55:52 +02:00
Fredrik Fornwall
6218d08037 Use https://termux.org/bootstrap-${ARCH}.zip
This will redirect to the desired version of the bootstrap zip in
bintray.
2019-04-08 19:06:57 +02:00
Leonid Plyushch
10fe238ddb termux service: make sure that styling is always applied whenever session starts 2019-04-07 20:53:38 +02:00
Leonid Plyushch
fe41cd486f sessions: failsafe session is now accessible via separate launcher icon
Also enables session autoclosing so no more "annoying" messages
about "process completed - press enter". There autoclosing will be
performed on exit codes '0' and '130'.

On Android TV devices old behaviour will be used - auto close enabled for all
sessions when amount of running sessions >1.
2019-04-07 20:51:25 +02:00
Fredrik Fornwall
bda80547ad Do not export LD_LIBRARY_PATH if bintray is used 2019-04-03 23:20:08 +02:00
Fredrik Fornwall
70a786613d Install from bintray on Android 7 and later 2019-04-03 23:02:31 +02:00
Fredrik Fornwall
e4220a7ab1 Update the Android gradle plugin 2019-04-03 23:02:31 +02:00
Leonid Plyushch
bd45837d93 sessions: do not clear TMPDIR if application was not started 2019-02-24 22:01:31 +02:00
Leonid Plyushch
ddf5341b86 native text input: make text selection visible 2019-02-24 22:01:22 +02:00
Yuvraj Saxena
85a48a79a3 termux-app: Fix a typo in TermuxPreferences 2019-02-19 20:22:26 +02:00
Tom Yan
e3512c957d fix termux/termux-app#995 on the java side 2019-02-13 22:50:29 +01:00
Fredrik Fornwall
330301899a Send \r instead of \n from native input text view (fixes #1020) 2019-02-09 23:14:44 +01:00
iamahuman
2a36b915cb Implement CSI 3 J - Clear transcript 2019-02-05 23:27:25 +01:00
Leonid Plyushch
c986a46fb9 extra keys: fix crash when clicking on "empty" (undefined) key 2019-02-05 23:26:08 +01:00
Fredrik Fornwall
ed8bd4b569 Update gradle wrapper 2019-02-03 22:38:55 +01:00
Fredrik Fornwall
7f4df8328c Toggle the extra keys view with VolumeUp+K
Using K as a shortcut for toggling extra Keyboard probably makes more
sense than VolumeUp+Q, although we support both for now.
2019-01-21 00:52:24 +01:00
Fredrik Fornwall
ad1ce585d9 Bump version to 0.66 2019-01-21 00:37:39 +01:00
Fredrik Fornwall
158908a3bf Keep help context menu item as last item 2019-01-21 00:34:02 +01:00
Fredrik Fornwall
d8f066dec4 Minor restructuring of TermuxPreferences 2019-01-21 00:33:03 +01:00
Leonid Plyushch
743225ab6a feature: allow to keep screen on 2019-01-21 00:24:49 +01:00
Fredrik Fornwall
b38ae24908 Make termux-reload-settings reload the extra keys directly 2019-01-21 00:12:07 +01:00
Fredrik Fornwall
70c1bddae0 Use constant for utf-8 encoding 2019-01-20 23:58:04 +01:00
Fredrik Fornwall
4df285013e Use constant for utf-8 encoding 2019-01-20 23:50:49 +01:00
Fredrik Fornwall
1e4fa8c045 Update the android gradle plugin 2019-01-20 23:49:03 +01:00
Fredrik Fornwall
31f2dc35bc Fix Android Studio lint warning 2019-01-20 23:48:23 +01:00
Henrik Grimler
fbb048ce56 Add issue templates
Hopefully gives less package bug reports opened in termux-app
2019-01-20 23:46:20 +01:00
Fredrik Fornwall
8c3a30027e Migrate to AndroidX 2019-01-11 21:19:06 +01:00
Fredrik Fornwall
a8c270f3c1 Use size in icon name in big icon 2019-01-11 00:36:47 +01:00
Trygve Aaberge
829414ac19 Reduce the height of viewpager/ExtraKeysView to previous height
Commit f0eeb47 introduced configurable extra keys and thus changed the
height of the viewpager to be multipled by the number of rows of extra
keys. However, the extra keys had two rows at the time, so the initial
height should have been changed to half of what it was now that it is
multiplied. This was not done, so the viewpager was twice the height it
should be after that change.
2019-01-11 00:32:22 +01:00
Fredrik Fornwall
8e0b8623bc Update art/copy-to-other-apps.sh 2019-01-10 23:58:18 +01:00
Fredrik Fornwall
e2bda2bb52 Add art/generate-big-icon.sh script 2018-11-23 02:22:38 +01:00
Fredrik Fornwall
8c7b1a60d2 Minor icon tweaks 2018-11-23 02:12:57 +01:00
Fredrik Fornwall
cb0710bb83 Use a solid color as background for adaptive icon 2018-11-17 23:34:14 +01:00
Fredrik Fornwall
b54c8276b4 Add .swp to gitignore 2018-11-17 23:26:26 +01:00
Fredrik Fornwall
7056945f5d Move to vector icon also on pre-26 2018-11-17 23:19:22 +01:00
Fredrik Fornwall
063ef02f2b Let the installer create directories when necessary
By creating directories when necessary before trying to install files we
depend on less details in how the bootstrap zip is constructed.
2018-11-17 22:50:27 +01:00
Fredrik Fornwall
6e6e4fd212 Use vector drawables for the launcher icon 2018-11-14 00:21:59 +01:00
Fredrik Fornwall
406438e391 Fix Android Studio lint warnings 2018-11-10 23:16:54 +01:00
Fredrik Fornwall
d1977479bd Update the Android Gradle plugin 2018-11-10 23:16:27 +01:00
Fredrik Fornwall
b4563023f6 Add casts in termux.c to silence lint warning 2018-11-08 22:31:06 +01:00
Fredrik Fornwall
095ed8b54f Use jint as return value from native function
This silences an error from the Android Studio lint.
2018-11-08 22:28:37 +01:00
Markus Gräb
af445f9618 Improve config parsing 2018-10-25 23:31:38 +02:00
Fredrik Fornwall
82f977fbf1 Remove unused function 2018-10-07 20:08:41 +02:00
Fredrik Fornwall
4b52cfbb93 Drop the round launcher icon
It's better to focus on the adaptive icon for the future. See #830.
2018-10-02 21:54:03 +02:00
Fredrik Fornwall
b61da23be7 Enable java 8 2018-09-29 00:49:05 +02:00
Fredrik Fornwall
35df3f72c9 Revert to java 8 for travis build 2018-09-29 00:32:43 +02:00
Fredrik Fornwall
d584502fef Update .travis.yml 2018-09-29 00:27:25 +02:00
Fredrik Fornwall
a691333c5e Update the gradle wrapper 2018-09-29 00:25:24 +02:00
Fredrik Fornwall
e053cf82ca Update the Android Gradle plugin 2018-09-29 00:22:44 +02:00
Fredrik Fornwall
076176a5f6 Add whole of .idea/ to .gitignore 2018-09-29 00:22:22 +02:00
Fredrik Fornwall
08c72f9a65 Remove .idea/ folder 2018-09-29 00:17:25 +02:00
Fredrik Fornwall
7593ddde38 Update README.md 2018-09-23 16:57:58 +02:00
Matthew Klein
6dce32aece Use 2 spaces for yaml files (.travis.yml) 2018-08-30 13:51:24 +02:00
Matthew Klein
5ff801c914 Use sdkmanager to download ndk-bundle 2018-08-29 13:54:09 +02:00
Matthew Klein
221046732b Update travis to android 28
This bumps build-tools from 27.0.3 to 28.0.2.
2018-08-29 13:54:09 +02:00
Fredrik Fornwall
07d6c9bd5e Remove unused import 2018-08-28 02:51:11 +02:00
Fredrik Fornwall
a9eddce672 compileSdkVersion = targetSdkVersion = 28 2018-08-28 02:50:52 +02:00
Leonid Plyushch
26c95f1397 reduce padding to 3dp (5 dp seems too many) 2018-08-28 02:39:41 +02:00
Leonid Plyushch
4eca639212 add some padding for TerminalView
Should fix https://github.com/termux/termux-app/issues/516
2018-08-28 02:39:41 +02:00
Leonid Plyushch
beb8a004e6 clean /tmp directory on cold start 2018-08-28 02:37:03 +02:00
Robert Vanden Eynde
3693e3c1b6 Default extra-keys has TAB after ESC and UP/DOWN arrow keys 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
99e8ffcf90 Add aliases for BACKSLASH QUOTE and APOSTROPHE 2018-08-28 02:34:36 +02:00
Henrik Grimler
0807600a2d ExtraKeys: use ' instead of \" in default key string 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
f74293e8fb Use extra-keys spelling and correct default setting 2018-08-28 02:34:36 +02:00
Henrik Grimler
b99d092305 ExtraKeys: Prevent app crash if user specifies different row lengths 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
af7515247b Fix refactoring, Ctrl, Alt, Fn keys work again 2018-08-28 02:34:36 +02:00
Henrik Grimler
ec77be00dc state -> SpecialButtonState state 2018-08-28 02:34:36 +02:00
Henrik Grimler
a854960476 Declare buttonText final String, dont't change value inside reload 2018-08-28 02:34:36 +02:00
Henrik Grimler
9db8948f23 Fix typos and build errors 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
c24167f6a5 Use utf-8 for config, Activate defaultCharDisplay, Fix Typo 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
49c051c8b7 Fix Typo (KeyEvent.KEYCODE_DEL and others) 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
55efdb2f56 Refactor Ctrl, Alt, Fn code 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
d03e420e75 Use LEFT instead of arrow keys in config, and include arrows characters for special keys (will be easily changed in the properties after) 2018-08-28 02:34:36 +02:00
Henrik Grimler
06968a9295 Extrakeys: fix typo 2018-08-28 02:34:36 +02:00
Henrik Grimler
244248b1a0 Extrakeys: make ― the same as - in some more places 2018-08-28 02:34:36 +02:00
Henrik Grimler
7187ed6950 Extrakeys: fix rebase error 2018-08-28 02:34:36 +02:00
Henrik Grimler
b3eabd9bad ExtraKeys: fix so app doesn't crash if ctrl/alt aren't in extrakeys
Otherwise we get:
AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.widget.CompoundButton.isChecked()' on a null object reference
AndroidRuntime:        at com.termux.app.ExtraKeysView.b(SourceFile:128)
2018-08-28 02:34:36 +02:00
Henrik Grimler
b51dd4f558 Extrakeys: show extra key row per default
Might help new users.
2018-08-28 02:34:36 +02:00
Henrik Grimler
f88b9c4629 ExtraKeys: add possibility for insert, delete, enter and - 2018-08-28 02:34:36 +02:00
neverwin
f0eeb4781b add support of configurable extra keys 2018-08-28 02:34:36 +02:00
Fredrik Fornwall
57a3a9b111 Request the FOREGROUND_SERVICE permission
From https://developer.android.com/guide/components/services:

"Apps that target Android 9 (API level 28) or higher and use foreground
services must request the FOREGROUND_SERVICE permission. This is a
normal permission, so the system automatically grants it to the
requesting app.

If an app that targets API level 28 or higher attempts to create a
foreground service without requesting FOREGROUND_SERVICE, the system
throws a SecurityException."
2018-08-26 01:07:33 +02:00
Fredrik Fornwall
8df73a3006 Set targetSdkVersion 28 2018-08-14 01:07:51 +02:00
Leonid Plyushch
963663e0cd close sessions without waiting for user input except the last one
An attempt to deal with these issues:

 * https://github.com/termux/termux-app/issues/627
 * https://github.com/termux/termux-app/issues/56
2018-08-11 23:09:16 +02:00
Lokesh Krishna
68a83ccf37 replaces foreground and background layers with nord color variants 2018-08-06 21:53:30 +02:00
Lokesh Krishna
db13ea02b6 Adds foreground and background layers 2018-08-02 00:41:18 +02:00
Lokesh Krishna
61a44dbfa8 Adds alternative drawable resource 2018-08-02 00:41:18 +02:00
Fredrik Fornwall
aaa92279ca Bump version to 0.65 2018-08-02 00:38:21 +02:00
Ico Doornekamp
365f9723cc - in onKeyDown() ignore keys when the Fn key is pressed
- in onKeyDown() return false when the key is not handled.

The above two changes fix the handling of Fn-key combo's on devices with
a physical keyboard, allowing the android system defined fallbacks from
`/system/usr/keychars/Generic.kcm` to be properly handled.

Fixes #731.

Original diagnosis and fix by Konehaltia.
2018-08-02 00:36:44 +02:00
Fredrik Fornwall
fdae272214 Bump version to 0.64 2018-07-02 00:05:04 +02:00
Leonid Plyushch
b7864d6ac2 deleteFolder(): check if passed argument is a symlink
Prevents possible data loss when user replaced directory '~/storage' with
a symlink.
2018-07-01 18:00:54 +02:00
Peter Vágner
d1f0c76db3 TerminalView: only use accessibility features when accessibility is
enabled when starting the view
2018-06-29 12:31:15 +02:00
Peter Vágner
5652624fc2 Call setContentDescription in onScreenUpdated rather than in onDraw.
That will be much less expensive.
2018-06-29 12:31:15 +02:00
Peter Vágner
35a9101f84 terminalview: add contentDescription to the view so accessibility
services can get the text currently being shown.
2018-06-29 12:31:15 +02:00
David xu
2b6a10712b fix button background and row height bug in api 21 2018-06-29 12:30:02 +02:00
Matthew Klein
c80e126b8f Add a .gitattributes file 2018-06-28 11:57:12 +02:00
Fredrik Fornwall
89048274dd Bump version to 0.63 2018-06-25 14:08:50 +02:00
Leonid Plyushch
d3b4d35b2a deleteFolder(): don't treat symlinks as directory 2018-06-25 14:08:28 +02:00
Fredrik Fornwall
4080f41cc7 Bump version to 0.62 2018-06-24 01:56:43 +02:00
Tom Yan
b4e2c99d46 Stop exporting PS1
PS1 is not supposed to be an env var and should be shell-specific.
We will set it appropriately with the init files of the shells.
2018-06-22 01:11:51 +02:00
Quasic
c5923201a4 fix indent with vim on termux
web interface would have worked, if I had deleted the tabs, first
This vim is adding tabs instead of matching the previous line's
indentation. Looking into fixing that now...
2018-06-22 00:33:35 +02:00
Quasic
bafd21bb39 fmt indentation 2018-06-22 00:33:35 +02:00
Quasic
0f20fab02c I think this is it 2018-06-22 00:33:35 +02:00
Quasic
c5ae5bb06a oops, should have local Java 2018-06-22 00:33:35 +02:00
Quasic
bbd46a763c Fix for #572 using recursive delete
Also see #578
2018-06-22 00:33:35 +02:00
David xu
dc145d65f8 minify long press interval 2018-06-22 00:29:51 +02:00
David xu
ce2d1c0d88 restore terminalview 2018-06-22 00:29:51 +02:00
David xu
f2f7f963e6 feat for - ― 2018-06-22 00:29:51 +02:00
David xu
2e53ef038e fix popup bug 2018-06-22 00:29:51 +02:00
xqdoo00o
8c82f43dce delete unnecss code 2018-06-22 00:29:51 +02:00
david
3a16f461e7 add symbol popup 2018-06-22 00:29:51 +02:00
xqdoo00o
594a5dfe25 comment unnecss code 2018-06-22 00:29:51 +02:00
david
d5e007dbb3 feat button color 2018-06-22 00:29:51 +02:00
david
f4335d3824 add some functional keys 2018-06-22 00:29:51 +02:00
Michał Bednarski
90b6f93697 Add fast path for TerminalRow.setChar
termux/termux-app#603
2018-06-22 00:21:31 +02:00
David Kramer
3bb2849a88 Sends broadcast on app open to notify addon termux receivers 2018-06-18 13:06:34 -06:00
Fredrik Fornwall
17c4a45212 Bump gradle dependencies 2018-06-15 00:22:52 +02:00
Fredrik Fornwall
6d9ffb6922 Bump version to 0.61 2018-06-15 00:21:27 +02:00
Fredrik Fornwall
8472fce8ba Use lower when looking up mime type from extension
Fixes #721.
2018-06-15 00:19:47 +02:00
Fredrik Fornwall
69d954a583 Update gradle configuration 2018-06-14 23:58:41 +02:00
easyaspi314 (Devin)
ec1087d56f Replace CRLF with CR as well.
This should replace both \r\n and \n with \r now.

"\r?\n" matches 0 or 1 \r and one \n, which should capture both
escape sequences.
2018-06-13 22:45:37 +02:00
easyaspi314 (Devin)
be6a73d862 Replace \n with \r when pasting (Fixes #678)
Termux will now properly send \r to the terminal instead of \n when pasting
multiline strings.

This fixes cat not repeating back lines and nano accidentally justifying
text (because \n maps to ^J), as well as other potential issues.

This matches the behavior of other terminals, such as iTerm2 which explicitly
does it here:
https://github.com/gnachman/iTerm2/blob/f8a5930/sources/iTermPasteHelper.m#L113

Signed-off-by: easyaspi314 (Devin) <easyaspi314@users.noreply.github.com>
2018-06-13 22:45:37 +02:00
l-jonas
80c81b274d Update build.gradle 2018-06-11 12:26:35 +02:00
Jonas L
cd9dbac548 Add Google Repository
The Google-Repository is required for compilation because some dependencies were removed from jcenter()
2018-06-11 12:26:35 +02:00
Fredrik Fornwall
0c837796f0 Update Android plugin for Gradle 2018-06-11 12:23:52 +02:00
Fredrik Fornwall
3417e37e8d Update gitignore for Android Studio 3.1 2018-04-02 01:38:47 +02:00
Fredrik Fornwall
31ba36e0fa Remove explicit buildToolsVersion
https://developer.android.com/studio/releases/gradle-plugin.html#behavior_changes
2018-04-02 00:58:02 +02:00
Fredrik Fornwall
c444f1fd28 Update gradle configuration 2018-04-02 00:40:16 +02:00
RangerMauve
9f36ed06b8 Make TerminalRenderer public
I want to use the renderer with a custom canvas without having to render to an Android View which requires a context and all sorts of stuff.

Can't currently do that because the renderer is package-private
2018-03-10 22:55:10 +01:00
Fredrik Fornwall
c2ab5bcd50 Bump some dependency versions 2018-02-06 23:30:28 +01:00
Fredrik Fornwall
48fab33b79 Bump version to 0.60 2018-01-26 01:00:37 +01:00
Marcel Dopita
8d7a67645b Fix notification icon color
Icons used in notifications/status bar are monochrome and should use only white color and transparency. It is displayed fine on Android emulators but it's shown in black on Samsung Galaxy S7 running Android 7.0.

After the change, status bar icon is white and the small icon in notification still uses the black color set via setColor(). Termux doesn't support devices running Android older than 5.0 but using white color in vector drawables would be also necessary to generate proper (white) bitmap icons.
2018-01-01 23:17:22 +01:00
Fredrik Fornwall
186b49d429 Work around travis API 27 SDK checksum mismatch
See https://github.com/travis-ci/travis-ci/issues/8874.
2018-01-01 23:16:43 +01:00
Fredrik Fornwall
40a2775f52 Bump version to 0.59 2017-12-09 00:59:19 +01:00
Fredrik Fornwall
f802d6001d Do not send mouse clicks out of range (fixes #501) 2017-12-09 00:58:31 +01:00
Fredrik Fornwall
50f66a12da Bump version to 0.58 2017-12-08 23:32:44 +01:00
Fredrik Fornwall
d8e6fd21d1 Set android:resizeableActivity="true"
Setting android:resizeableActivity="true" should AFAIK not be needed
as https://developer.android.com/guide/topics/ui/multi-window.html
states that:

  If your app targets API level 24, but you do not specify a value
  for this attribute, the attribute's value defaults to true.

However, on a Galaxy S8 running Android 7.0 this attribute is needed
in order to have resizeable windows when in Samsung Dex mode.

Fixes #309.
2017-12-08 23:30:45 +01:00
Fredrik Fornwall
0964d83572 Bump com.android.support dependencies 2017-12-07 10:25:02 +01:00
Fredrik Fornwall
cee0466dd7 Bump version to 0.57 2017-12-07 01:48:51 +01:00
Fredrik Fornwall
de4f334e24 Remove the fullscreen setting
Trying to use fullscreen doesn't work well in a multi-windowed world
and makes using translucent navigation and status bars more complicated.
2017-12-07 01:46:16 +01:00
Fredrik Fornwall
ab205ae05b Update build tools in travis 2017-12-07 01:45:30 +01:00
Fredrik Fornwall
b29b24f507 Add android.max_aspect=10.0 metadata
This is needed to mark the app with

	"This app is optimized to run in full screen"

when run on a Samsung Galaxy S8.
2017-12-07 01:39:43 +01:00
Fredrik Fornwall
b3472e9e62 Disable the "quick scale" gesture
Disable the default android behaviour where a double tap followed by a
swipe is interpreted as a zoom gesture. Most people seem not to know
about it and hit it by mistake, see #495 for an example.
2017-12-07 01:36:29 +01:00
Fredrik Fornwall
ab59e08959 Update build tools 2017-12-07 01:12:39 +01:00
Fredrik Fornwall
8b566485e8 Do not clear the sessions on ACTION_STOP_SERVICE
There is no point doing so and it may cause problems with the list
adapter.
2017-11-26 00:56:13 +01:00
Fredrik Fornwall
231c02a0c7 Check for null mTermSession in inputCodePoint() 2017-11-26 00:50:13 +01:00
Fredrik Fornwall
65f30e77ba Add tools:ignore="AppLinkUrlError" 2017-11-25 22:59:03 +01:00
Fredrik Fornwall
686677ae45 Update android support dependencies 2017-11-25 22:58:46 +01:00
Fredrik Fornwall
85037a75a6 Remove redundant cast 2017-11-25 22:58:23 +01:00
Fredrik Fornwall
6025afc2c0 Update the Android Plugin for Gradle to 3.0.1 2017-11-24 23:11:55 +01:00
Fredrik Fornwall
a4e4f76775 Remove some Android Studio warnings 2017-11-22 01:27:14 +01:00
Fredrik Fornwall
02af113dda Bump app version to 0.56 2017-11-19 20:54:16 +01:00
Fredrik Fornwall
fc4ef838bf Configure proguard to keep line numbers 2017-11-19 20:53:56 +01:00
Fredrik Fornwall
86bd3ca21b Bump app version to 0.54 2017-11-19 15:27:17 +01:00
Fredrik Fornwall
367398bafb Update gradle configuration 2017-11-19 15:27:00 +01:00
Lauri Tirkkonen
dd502e55f8 set default notification priority to low
since we're a foreground service, in oreo we already get a higher
priority; see
https://developer.android.com/reference/android/app/NotificationManager.html#IMPORTANCE_MIN
2017-11-12 23:44:14 +01:00
Fredrik Fornwall
798125ef7a Remove outdated comment 2017-11-12 23:06:28 +01:00
Fredrik Fornwall
5126f06e06 Update .travis.yml to latest setup 2017-11-12 22:30:09 +01:00
Fredrik Fornwall
0bdbf314ef Update gradle configuration 2017-11-07 04:25:48 +01:00
Fredrik Fornwall
2deb9899bd Update buildToolsVersion to 27.0.1 2017-11-07 04:13:26 +01:00
Fredrik Fornwall
694ccc38c4 Remove WakefulBroadcastReceiver usage 2017-11-04 23:38:49 +01:00
Fredrik Fornwall
c949940374 Update support deps 2017-11-02 01:56:42 +01:00
Fredrik Fornwall
d09f70de1e Use a notification channel on Android O 2017-11-01 21:15:41 +01:00
Fredrik Fornwall
ac338ce2c5 Update gradle to 4.3 2017-10-31 19:07:00 +01:00
Fredrik Fornwall
092a83a688 Validate file path in TermuxOpenReceiver 2017-10-28 16:19:58 +02:00
Fredrik Fornwall
3533b13de8 Get rid of two android studio warnings 2017-10-28 11:05:43 +02:00
Fredrik Fornwall
d68a0f05be Update gradle configuration 2017-10-28 11:05:43 +02:00
Fredrik Fornwall
9e5293a08e Do not scroll when below bottom margin 2017-10-28 11:05:43 +02:00
Fredrik Fornwall
3f04a0a0d5 Bump build tools version in .travis.yml 2017-10-28 11:05:43 +02:00
Fredrik Fornwall
e558f824c5 Update gradle configuration 2017-10-28 11:05:43 +02:00
Auxilus
8ff72ddd8b replace help url with wiki url
HELP page wiki changed to https://wiki.termux.com/wiki/Main_Page as it now has more info than http://termux.com/help.html
2017-09-26 18:14:19 +02:00
Fredrik Fornwall
3e05356a69 Merge pull request #421 from sdrausty/master
Added two important links to README.md
2017-09-20 22:37:49 +02:00
Fredrik Fornwall
82673d3f82 Update gradle configuration 2017-09-17 11:47:12 +02:00
Fredrik Fornwall
f2757fdb76 Merge pull request #424 from EdwardBetts/patch-1
correct spelling mistake
2017-09-15 00:21:47 +02:00
Edward Betts
81fb669d0e correct spelling mistake 2017-09-13 16:16:08 +01:00
S D Rausty
4a45b1b617 Commit on 20170911 at 13:23:22 and at 1505150602 in Unix time. 2017-09-11 13:23:22 -04:00
S D Rausty
62b08b3cf1 Merge pull request #1 from termux/master
Hello
2017-09-10 13:24:32 -04:00
Fredrik Fornwall
1e83ea3151 Merge pull request #412 from Auxilus/patch-2
Added wiki link
2017-09-06 16:26:54 +02:00
Auxilus
9c42fdb3d6 Added wiki link 2017-09-05 10:43:30 +05:30
Fredrik Fornwall
42e3163e92 Update gradle to 4.1 2017-08-24 20:49:43 +02:00
Fredrik Fornwall
68912139f6 Additional REP test 2017-08-23 23:29:59 +02:00
Fredrik Fornwall
edc92bbcbb Start supporting the REP escape sequence 2017-08-13 16:14:31 +02:00
Fredrik Fornwall
d7520642de Update gradle 2017-07-29 03:05:48 +02:00
Fredrik Fornwall
adae111d5c Update gradle config 2017-07-09 21:07:18 +02:00
Fredrik Fornwall
dc9272790b Merge pull request #301 from kzlin129/android-things-auto-launch
[Android-things] Launch TermuxActivity automatically on boot
2017-06-26 02:04:03 +02:00
Fredrik Fornwall
05ea2ea238 Bump version to 0.53 2017-06-26 01:30:08 +02:00
Fredrik Fornwall
ad293562bc Fix possible crash in the copy/paste/more dialog 2017-06-26 01:29:05 +02:00
Fredrik Fornwall
f9a565d1e0 Ignore C1 control codes
C1 control codes are not used nowadays and just risks messing up
the terminal when they are used by accident.
2017-06-26 01:28:18 +02:00
Will Lin
5b3909cb73 Update AndroidManifest.xml with expanded comment 2017-06-17 15:39:23 +08:00
Fredrik Fornwall
7913e765d5 Bump version to 0.52 2017-06-13 16:44:33 +02:00
Fredrik Fornwall
ed3a3269d8 Merge pull request #339 from bumper314/master
Add Multi Window support for Samsung devices
2017-06-13 10:13:26 +02:00
Steven Audette
dc3994d2cf Add Multi Window support for Samsung devices
A single meta-data entry is enough to make Termux "Multi Window" aware on supported Samsung devices.
Tested and confirmed working on Samsung Note Pro 12.2 running Android 5.1.1.

This addition should not interfer with Android 6+'s split screen feature.
Tested and confirmed split screen functions as normal on a Nexus 6 running Android 7.0.

This addition should go unnoticed on non-Smasung Android 5 devices.
Tested and confirmed no functionality visible on Nexus 5 running Android 5.1.
2017-06-12 01:47:27 -06:00
Fredrik Fornwall
c972377bd1 Update gradle config 2017-06-11 22:47:59 +02:00
Fredrik Fornwall
d4be782c03 Bump version to 0.51 2017-06-05 22:49:11 +02:00
Fredrik Fornwall
c29909726c Bump terminal-emulator libraryVersion to 0.50 2017-06-05 22:45:44 +02:00
Fredrik Fornwall
45bac89298 Remove unused xmlns:android declaration 2017-06-05 22:45:15 +02:00
Fredrik Fornwall
c06770c353 Merge pull request #330 from whydoubt/cursor_preserve_color
Make cursor save/restore affect color attributes
2017-06-05 22:35:39 +02:00
Fredrik Fornwall
52a627efc8 Merge pull request #331 from whydoubt/fix_parameter_fc
Fix FC from too many control code parameters
2017-06-05 22:35:32 +02:00
Jeff Smith
2e9383720c Make cursor save/restore affect color attributes
SGR attributes are stored in three variables: mEffect, mForeColor, and
mBackColor.  Saving/restoring the cursor only preserves mEffect.

Change the cursor save/restore methods to additionally preserve
mForeColor and mBackColor.  This affects both 'explit' saving/restoring
the cursor and switching to/from the alternate screen buffer.
2017-06-03 17:45:36 -05:00
Jeff Smith
58f9f1be71 Add tests for cursor save/restore 2017-06-03 17:45:36 -05:00
Jeff Smith
058441dda6 Fix FC from too many control code parameters
When the number of parameters in a CSI control code exceeds the size of
the mArgs array, the code may attempt to read past the end of the array,
resulting in a force close of the app.

Stop the index from moving past the last element of the mArgs array.
2017-06-03 00:15:09 -05:00
Jeff Smith
888802a519 Add test for CSI followed by many parameters 2017-06-03 00:14:20 -05:00
Fredrik Fornwall
1a09b6d2a6 Change android:extractNativeLibs to false
We're trying this to see if that fixes an F-Droid build issue.

See #319.
2017-05-18 13:25:41 +02:00
Fredrik Fornwall
9d3f7734a1 Update android build tools 2017-05-16 11:11:57 +02:00
Fredrik Fornwall
61f766b59f Bump build tools in .travis.yml 2017-05-16 00:21:15 +02:00
Fredrik Fornwall
3f08376881 Bump version to 0.49 2017-05-15 23:42:55 +02:00
Fredrik Fornwall
cf06e70429 Update gradle configuration 2017-05-15 23:42:30 +02:00
Fredrik Fornwall
0714e435cb Do not show a Toast on clipboard text
Fixes https://github.com/termux/termux-app/issues/149.
2017-05-15 23:42:13 +02:00
Fredrik Fornwall
c26315185f Export TMPDIR to $PREFIX/tmp
Fixes https://github.com/termux/termux-packages/issues/1010.
Fixes https://github.com/termux/termux-app/issues/306.
2017-05-15 23:40:55 +02:00
Will Lin
1be5139253 [Android-things] Launch TermuxActivity automatically on boot 2017-04-18 20:13:47 +08:00
Fredrik Fornwall
adc43c40c5 Perform haptic feedback on extra keys
Fixes #269.
2017-04-16 12:18:13 +02:00
Fredrik Fornwall
779b1ca1f8 Update the Android gradle plugin to 2.3.1 2017-04-05 09:10:54 +02:00
Fredrik Fornwall
e375f99b0c Remove float/ module from .idea/gradle.xml 2017-04-05 09:09:48 +02:00
Fredrik Fornwall
0ac55cd77a Remove float/
The Termux:Float app has a new home at:

https://github.com/termux/termux-float
2017-04-04 23:39:05 +02:00
Fredrik Fornwall
4aefa5049b Add gradle setup to publish to jcenter 2017-04-04 23:31:00 +02:00
Fredrik Fornwall
5b14124258 Make GestureAndScaleRecognizer non-public 2017-04-04 20:56:19 +02:00
Fredrik Fornwall
1a9c38374c Fix some invalid html in javadoc 2017-04-02 15:11:56 +02:00
Fredrik Fornwall
eefd504f08 Use getLocationOnScreen() to calculate position
This avoids problem when the window has been prevented from scrolling
outside of the screen, or when the touch keyboard has made it move.
2017-04-02 14:46:47 +02:00
Fredrik Fornwall
19f838e3d1 Show window if launched when hidden 2017-04-02 14:46:00 +02:00
Fredrik Fornwall
63adb2b132 Rename TerminalKeyListener to TerminalViewClient 2017-04-02 14:25:34 +02:00
Fredrik Fornwall
7d3a988d2f Restructure library modules
terminal/ -> terminal-emulator/
view/ -> terminal-view/
2017-04-02 14:21:36 +02:00
Fredrik Fornwall
5cd705d6d1 Reformat all code in float/, getting rid of tabs 2017-04-02 14:10:26 +02:00
Fredrik Fornwall
af8b269b51 Work on Termux:Float input handling 2017-04-02 14:09:43 +02:00
Fredrik Fornwall
8c220eaaea Do not call long press listener when scaling 2017-04-02 14:07:19 +02:00
Fredrik Fornwall
0142e1ed0e Update Termux:Float launcher icon 2017-04-02 07:33:06 +02:00
Fredrik Fornwall
ac0a349ed9 Remove versionCode&versionName from libraries 2017-04-02 07:12:03 +02:00
Fredrik Fornwall
548b0916b6 Clean up {terminal,view}/build.gradle 2017-04-02 06:58:59 +02:00
Fredrik Fornwall
009de5a3ee Split up into modules and add float module
Split the app/ module into three modules

terminal/ - Terminal emulator library module.
view/ - Terminal view library module (depending on terminal/).
app/ - The main Termux app (depending on view/).

Also add the

float/ - The Termux:Float app (depending on view/).
2017-04-01 19:06:02 +02:00
Fredrik Fornwall
41d0d60017 Respect content type termux-open for url:s 2017-03-28 23:52:52 +02:00
Fredrik Fornwall
12ac0fa73c Add android:extractNativeLibs="false" to manifest 2017-03-12 20:55:58 +01:00
Fredrik Fornwall
835dfc0276 Avoid synthetic accessor method 2017-03-06 01:53:09 +01:00
Fredrik Fornwall
f60316835f Fix some android studio lint warnings 2017-03-06 01:47:08 +01:00
Fredrik Fornwall
f74a091be6 Remove useless continue statement 2017-03-06 01:44:16 +01:00
Fredrik Fornwall
dd6cb5221d Work around Android < 7.0 wifi manager leak
http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak
2017-03-06 01:42:33 +01:00
Fredrik Fornwall
cab6df5c0e Update gradle config 2017-03-06 01:41:12 +01:00
Fredrik Fornwall
f6f0809558 Bump version to 0.48 2017-03-02 00:32:09 +01:00
Fredrik Fornwall
11ed7e45d8 Fix crash when opening URL:s in Android < 7.0
In versions of Android earlier than 7.0 the FLAG_ACTIVITY_NEW_TASK
is needed when starting an activity from a non-activity context.

This was removed in Android 7.0 (possibly by mistake), see
https://code.google.com/p/android/issues/detail?id=226647.

Fixes #802
2017-03-02 00:29:17 +01:00
Fredrik Fornwall
ed1874db05 Bump version to 0.47 2017-02-28 00:47:16 +01:00
Fredrik Fornwall
cb60803a80 Tweak launcher size 2017-02-28 00:46:21 +01:00
Fredrik Fornwall
fc92a27cb2 Extended keyboard: allow inline input
Ff the buffer is empty, send a newline, otherwise send the content of
the buffer with the newline stripped. This way <Enter><Enter> means
"insert the buffer content and send a newline", while a single <Enter>
means just "insert the buffer content". Fixes #261.
2017-02-28 00:42:36 +01:00
Fredrik Fornwall
29e62e608f Use standard actions 2017-02-28 00:42:07 +01:00
Fredrik Fornwall
8a7f93d722 Launch $PREFIX/bin/login by default
The login command takes care of both showing the etc/motd and
launching the login shell.
2017-02-28 00:41:20 +01:00
Fredrik Fornwall
420683fe65 Add built-in broadcast receiver to open files 2017-02-27 22:31:11 +01:00
Fredrik Fornwall
a3256ed551 Travis testing 2017-02-05 22:25:37 +01:00
Fredrik Fornwall
57add98e3c More attempts to get coverity scan working 2017-02-05 21:44:47 +01:00
Fredrik Fornwall
6e5c04e04f Remove the travis certificate workaround 2017-02-05 18:24:59 +01:00
Fredrik Fornwall
528a05ef61 Remove the welcome dialog
Instead of a modal dialog to be dismissed without any way to recall
it, the initial information will be shown in the terminal itself.
2017-02-04 11:31:04 +01:00
227 changed files with 18424 additions and 3855 deletions

View File

@@ -14,3 +14,7 @@ insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4
[*.y{a,}ml]
indent_size = 2
indent_style = space

5
.gitattributes vendored Normal file
View File

@@ -0,0 +1,5 @@
* text=auto
*.bat eol=crlf
*.gradle eol=lf
*.mk eol=lf
*.sh eol=lf

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
patreon: termux
custom: https://paypal.me/fornwall

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,35 @@
---
name: Bug report
about: Create a report to help us improve Termux application
---
<!--
IMPORTANT:
1. Support of Android 5.x - 6.x is finished.
2. Fill the template AFTER comments.
-->
**Problem description**
<!--
A clear and concise description of what the problem is.
You may post screenshots in addition to description.
-->
**Steps to reproduce**
<!--
Steps to reproduce the behavior. Please post all necessary
commands that are needed to reproduce the issue.
-->
**Expected behavior**
<!--
A clear and concise description of what you expected to happen.
-->
**Additional information**
* Termux application version:
* Android OS version:
* Device model:

View File

@@ -0,0 +1,22 @@
---
name: Feature request
about: Suggest a new feature for Termux application
---
<!--
IMPORTANT:
1. Support of Android 5.x - 6.x is finished.
2. Fill the template AFTER comments.
-->
**Feature description**
<!--
Describe the feature and why you want it.
-->
**Reference implementation**
Does another app/terminal emulator have this feature?
Provide links to more background information.

26
.github/workflows/debug_build.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Build
on:
push:
branches:
- master
- android-10
pull_request:
branches:
- master
- android-10
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v2
- name: Build
run: |
./gradlew assembleDebug
- name: Store generated APK file
uses: actions/upload-artifact@v2
with:
name: termux-app
path: ./app/build/outputs/apk/debug

View File

@@ -0,0 +1,19 @@
name: "Validate Gradle Wrapper"
on:
push:
branches:
- master
- android-10
pull_request:
branches:
- master
- android-10
jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1

26
.github/workflows/publish_libraries.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Publish library packages
on:
push:
branches:
- master
paths:
- 'terminal-emulator/build.gradle'
- 'terminal-view/build.gradle'
- 'termux-shared/build.gradle'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v2
- name: Perform release build
run: |
./gradlew assembleRelease
- name: Publish libraries on Github Packages
env:
GH_USERNAME: xeffyr
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
./gradlew publish

21
.github/workflows/run_tests.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Unit tests
on:
push:
branches:
- master
- android-10
pull_request:
branches:
- master
- android-10
jobs:
testing:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v2
- name: Execute tests
run: |
./gradlew test

18
.gitignore vendored
View File

@@ -7,6 +7,8 @@ build/
*.apk
*.so
.externalNativeBuild
.cxx
*.zip
# Crashlytics configuations
com_crashlytics_export_strings.xml
@@ -20,19 +22,8 @@ local.properties
# Signing files
.signing/
# User-specific configurations
.idea/libraries/
.idea/workspace.xml
.idea/tasks.xml
.idea/.name
.idea/compiler.xml
.idea/copyright/profiles_settings.xml
.idea/encodings.xml
.idea/misc.xml
.idea/modules.xml
.idea/scopes/scope_settings.xml
.idea/vcs.xml
.idea/dictionaries/
# Intellij
.idea/
*.iml
# OS-specific files
@@ -41,5 +32,6 @@ local.properties
._*
.Spotlight-V100
.Trashes
.swp
ehthumbs.db
Thumbs.db

View File

@@ -1,229 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="android" withSubpackages="true" static="false" />
<emptyLine />
<package name="com" withSubpackages="true" static="false" />
<emptyLine />
<package name="junit" withSubpackages="true" static="false" />
<emptyLine />
<package name="net" withSubpackages="true" static="false" />
<emptyLine />
<package name="org" withSubpackages="true" static="false" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
</value>
</option>
<option name="RIGHT_MARGIN" value="100" />
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
</AndroidXmlCodeStyleSettings>
<Objective-C-extensions>
<option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
<option name="RELEASE_STYLE" value="IVAR" />
<option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" />
<pair source="c" header="h" />
</extensions>
</Objective-C-extensions>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</value>
</option>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
</component>
</project>

19
.idea/gradle.xml generated
View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="disableWrapperSourceDistributionNotification" value="true" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@@ -1,71 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AndroidLintLogConditional" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AndroidLintNegativeMargin" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DeprecatedAPI" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DuplicateSwitchCase" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="EmptyStatementBody" enabled="false" level="WARNING" enabled_by_default="false">
<option name="m_reportEmptyBlocks" value="true" />
</inspection_tool>
<inspection_tool class="EndlessLoop" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="EqualityInConditionalOperator" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="Finalize" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreTrivialFinalizers" value="true" />
</inspection_tool>
<inspection_tool class="FinalizeNotProtected" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FormatSpecifiers" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="FunctionImplicitDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HidesUpperScope" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HidingNonVirtualFunction" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ImplicitIntegerAndEnumConversion" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ImplicitPointerAndIntegerConversion" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="IncompatibleEnums" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="IncompatibleInitializers" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="IncompatiblePointers" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="InstanceofChain" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreInstanceofOnLibraryClasses" value="false" />
</inspection_tool>
<inspection_tool class="KRUnspecifiedParameters" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LocalValueEscapesScope" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
</inspection_tool>
<inspection_tool class="MissingReturn" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MissingSwitchCase" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NotImplementedFunctions" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NotInitializedVariable" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NotSuperclass" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCDFAInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCLoopDoesntUseConditionVariableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCSimplifyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedGlobalDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedStructInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedTemplateParameterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OnDemandImport" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PrivateMemberAccessBetweenOuterAndInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ResourceNotFoundInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SamePackageImport" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SignednessMismatch" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="UnnecessaryFullyQualifiedName" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreJavadoc" value="false" />
</inspection_tool>
<inspection_tool class="UnreachableCode" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedExpressionResult" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedImportStatement" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedLocalization" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedParameter" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedValue" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ValueMayNotFitIntoReceiver" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="VariableNotUsedInsideIf" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</settings>
</component>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@@ -1,35 +0,0 @@
sudo: true
language: android
jdk: oraclejdk8
env:
global:
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
# via the "travis encrypt" command using the project repo's public key
- secure: "ACnFJxw0VusS2lnGXL+epP/CNJmftWS39YcPdgN2EurWw5ZfXSo7vi+zpMB+11IBS3LQyLFFUambi2N9L4lbReZkHVkoVcZFGZlwbXNTAeqT8CABPTcuOyEOZU4bJwqeYU87ztYipENMLNECaZrgWx5odbWLKnSJQw7Zkb4ArCstfXfYk9u8q49ThRxQyGwHW2xKp1an5aa+3Y6IY+ywsSHw6AvXbyFH078Kolxy86caagczcfmKcMi15QYzwAvFggUphvsO3M5PHJMQXuaNlQxDcQRGUEXsK8aZE0dPH5PB97SFjDALZqI7NEpjZAk5htWjX48ssW064LDbjcBg/ZLgDd8R8uhA159NVZgvcnP2czCn6pmggx1sW5MBmcj7i+bJS2ejaMO+KoovWlVvsch742H5QR6rQaNkjDZRsGVLYvJaR1gBLs898UoT1hcHWoqLVR22r2VFo7OWWCRfNRvZuZDR2HIrYRdFvn8P3nWVMkvXwgsOlxWG5sN+yQqW+6lZS7hivsFhtYs4CkRdoZIan3Qvi/CkY8Lg+ESkZ3IJ0NnId8qOWH+8Xl1sqZ7xlsWTd1sYYHlpvkdvqw1HNLP22EpwwKW5Kb5zBEd/qs3o1OO0Tqa0MR6JpgGdHHRk1iZ25+qTfRVP06vO2RXsgAx4SZfO7DyB0QZn8tGNMMI="
android:
components:
- platform-tools
- tools
- build-tools-25.0.2
- android-25
- extra-android-m2repository
before_install:
- git clone https://github.com/urho3d/android-ndk.git $HOME/android-ndk
- export ANDROID_NDK_HOME=$HOME/android-ndk
- echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-certificates.crt
script:
- ./gradlew testDebugUnitTest
addons:
coverity_scan:
project:
name: "termux/termux-app"
description: "Terminal emulator and Linux environment for Android"
notification_email: fredrik@fornwall.net
build_command_prepend: "./gradlew clean"
build_command: "./gradlew build"
branch_pattern: master

3
LICENSE.md Normal file
View File

@@ -0,0 +1,3 @@
Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html).
Contains code from `Terminal Emulator for Android` by which is released under [the Apache License 2.0](https://www.apache.org/licenses/).

View File

@@ -1,33 +1,70 @@
Termux app
==========
[![Travis build status](https://travis-ci.org/termux/termux-app.svg?branch=master)](https://travis-ci.org/termux/termux-app)
# Termux application
[![Build status](https://github.com/termux/termux-app/workflows/Build/badge.svg)](https://github.com/termux/termux-app/actions)
[![Testing status](https://github.com/termux/termux-app/workflows/Unit%20tests/badge.svg)](https://github.com/termux/termux-app/actions)
[![Join the chat at https://gitter.im/termux/termux](https://badges.gitter.im/termux/termux.svg)](https://gitter.im/termux/termux)
[Termux](https://termux.com) is an Android terminal app and Linux environment.
[Termux](https://termux.com) is an Android terminal application and Linux environment.
* [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux)
* [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux)
* [Termux Help](http://termux.com/help/)
* [Termux Google+ community](http://termux.com/community/)
- [Termux Reddit community](https://reddit.com/r/termux)
- [Termux Wiki](https://wiki.termux.com/wiki/)
- [Termux Twitter](http://twitter.com/termux/)
Note that this repository is for the app itself (the user interface and the terminal emulation). For the packages installable inside the app, see [termux/termux-packages](https://github.com/termux/termux-packages)
Note that this repository is for the app itself (the user interface and the
terminal emulation). For the packages installable inside the app, see
[termux/termux-packages](https://github.com/termux/termux-packages)
License
=======
Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html). Contains code from `Terminal Emulator for Android` which is released under [the Apache License 2.0](https://www.apache.org/licenses/).
***
Terminal resources
==================
* [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
* [vt100.net](http://vt100.net/)
* [Terminal codes (ANSI and terminfo equivalents)](http://wiki.bash-hackers.org/scripting/terminalcodes)
**@termux is looking for Termux Application maintainer for implementing new features,
fixing bugs and reviewing pull requests since current one (@fornwall) is inactive.**
Terminal emulators
==================
* VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal. [Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+), and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED).
* iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2), [Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html) (which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)).
* Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository), in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests), [Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole) and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole).
* hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm), including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js), and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm).
* xterm: The grandfather of terminal emulators. [Source](http://invisible-island.net/datafiles/release/xterm.tar.gz).
* Connectbot: Android SSH client. [Source](https://github.com/connectbot/connectbot)
* Android Terminal Emulator: Android terminal app which Termux terminal handling is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator).
Issue https://github.com/termux/termux-app/issues/1072 needs extra attention.
***
## Installation
Termux application can be obtained from [F-Droid](https://f-droid.org/en/packages/com.termux/).
Additionally we provide per-commit debug builds for those who want to try
out the latest features or test their pull request. This build can be obtained
from one of the workflow runs listed on [Github Actions](https://github.com/termux/termux-app/actions)
page.
Signature keys of all offered builds are different. Before you switch the
installation source, you will have to uninstall the Termux application and
all currently installed plugins.
## Terminal resources
- [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
- [vt100.net](http://vt100.net/)
- [Terminal codes (ANSI and terminfo equivalents)](http://wiki.bash-hackers.org/scripting/terminalcodes)
## Terminal emulators
- VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal.
[Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+),
and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED).
- iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2),
[Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html)
(which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)).
- Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository),
in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests),
[Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole)
and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole).
- hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm),
including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js),
and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm).
- xterm: The grandfather of terminal emulators.
[Source](http://invisible-island.net/datafiles/release/xterm.tar.gz).
- Connectbot: Android SSH client. [Source](https://github.com/connectbot/connectbot)
- Android Terminal Emulator: Android terminal app which Termux terminal handling
is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator).

View File

@@ -1,20 +1,42 @@
apply plugin: 'com.android.application'
plugins {
id "com.android.application"
}
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
compileSdkVersion project.properties.compileSdkVersion.toInteger()
ndkVersion project.properties.ndkVersion
dependencies {
compile 'com.android.support:support-annotations:25.1.0'
compile "com.android.support:support-v4:25.1.0"
implementation "androidx.annotation:annotation:1.2.0"
implementation "androidx.core:core:1.5.0-rc01"
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.viewpager:viewpager:1.0.0"
implementation "com.google.guava:guava:24.1-jre"
implementation "io.noties.markwon:core:$markwonVersion"
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
implementation "io.noties.markwon:linkify:$markwonVersion"
implementation "io.noties.markwon:recycler:$markwonVersion"
implementation project(":terminal-view")
implementation project(":termux-shared")
}
defaultConfig {
applicationId "com.termux"
minSdkVersion 21
targetSdkVersion 25
versionCode 46
versionName "0.46"
minSdkVersion project.properties.minSdkVersion.toInteger()
targetSdkVersion project.properties.targetSdkVersion.toInteger()
versionCode project.properties.termuxVersionCode.toInteger()
versionName project.properties.termuxVersion
manifestPlaceholders.TERMUX_PACKAGE_NAME = "com.termux"
manifestPlaceholders.TERMUX_APP_NAME = "Termux"
manifestPlaceholders.TERMUX_API_APP_NAME = "Termux:API"
manifestPlaceholders.TERMUX_BOOT_APP_NAME = "Termux:Boot"
manifestPlaceholders.TERMUX_FLOAT_APP_NAME = "Termux:Float"
manifestPlaceholders.TERMUX_STYLING_APP_NAME = "Termux:Styling"
manifestPlaceholders.TERMUX_TASKER_APP_NAME = "Termux:Tasker"
manifestPlaceholders.TERMUX_WIDGET_APP_NAME = "Termux:Widget"
externalNativeBuild {
ndkBuild {
@@ -25,6 +47,16 @@ android {
ndk {
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
}
signingConfigs {
debug {
storeFile file('dev_keystore.jks')
keyAlias 'alias'
storePassword 'xrj45yWGLbsO7W0v'
keyPassword 'xrj45yWGLbsO7W0v'
}
}
buildTypes {
@@ -33,15 +65,106 @@ android {
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
signingConfig signingConfigs.debug
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
path "src/main/cpp/Android.mk"
}
}
lintOptions {
disable 'ProtectedPermissions'
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
dependencies {
testCompile 'junit:junit:4.12'
testImplementation "junit:junit:4.13.2"
testImplementation "org.robolectric:robolectric:4.4"
}
task versionName {
doLast {
print android.defaultConfig.versionName
}
}
def downloadBootstrap(String arch, String expectedChecksum, String version) {
def digest = java.security.MessageDigest.getInstance("SHA-256")
def localUrl = "src/main/cpp/bootstrap-" + arch + ".zip"
def file = new File(projectDir, localUrl)
if (file.exists()) {
def buffer = new byte[8192]
def input = new FileInputStream(file)
while (true) {
def readBytes = input.read(buffer)
if (readBytes < 0) break
digest.update(buffer, 0, readBytes)
}
def checksum = new BigInteger(1, digest.digest()).toString(16)
if (checksum == expectedChecksum) {
return
} else {
logger.quiet("Deleting old local file with wrong hash: " + localUrl)
file.delete()
}
}
def remoteUrl = "https://github.com/termux/termux-packages/releases/download/bootstrap-" + version + "/bootstrap-" + arch + ".zip"
logger.quiet("Downloading " + remoteUrl + " ...")
file.parentFile.mkdirs()
def out = new BufferedOutputStream(new FileOutputStream(file))
def connection = new URL(remoteUrl).openConnection()
connection.setInstanceFollowRedirects(true)
def digestStream = new java.security.DigestInputStream(connection.inputStream, digest)
out << digestStream
out.close()
def checksum = new BigInteger(1, digest.digest()).toString(16)
if (checksum != expectedChecksum) {
file.delete()
throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum)
}
}
clean {
doLast {
def tree = fileTree(new File(projectDir, 'src/main/cpp'))
tree.include 'bootstrap-*.zip'
tree.each { it.delete() }
}
}
task downloadBootstraps() {
doLast {
def version = "2021.04.13-r1"
downloadBootstrap("aarch64", "ff82e5755d947cd1f3e0b30916d125c6ddd8ba3254801ca7499d73653417e158", version)
downloadBootstrap("arm", "53a7df2d6d0a36a8c9ab5259c8b5457c93b8bae8aec2321a470236b6da54e59a", version)
downloadBootstrap("i686", "f0e1399a13ebed6c5229fde161f9848d9f5eeae7b8cd82f31250a813b52e371", version)
downloadBootstrap("x86_64", "e36c4d8c933dc12b3f48937b7747c7a4dcfaa70f0dd89ad5e8b4465930075ae9", version)
}
}
afterEvaluate {
android.applicationVariants.all { variant ->
variant.javaCompileProvider.get().dependsOn(downloadBootstraps)
}
}

BIN
app/dev_keystore.jks Normal file

Binary file not shown.

View File

@@ -7,11 +7,6 @@
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
-dontobfuscate
#-renamesourcefileattribute SourceFile
#-keepattributes SourceFile,LineNumberTable

View File

@@ -1,13 +0,0 @@
package com.termux;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View File

@@ -1,60 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.termux"
android:installLocation="internalOnly"
android:sharedUserId="com.termux"
android:sharedUserLabel="@string/shared_user_label" >
android:sharedUserId="${TERMUX_PACKAGE_NAME}"
android:sharedUserLabel="@string/shared_user_label">
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<permission
android:name="${TERMUX_PACKAGE_NAME}.permission.RUN_COMMAND"
android:description="@string/permission_run_command_description"
android:icon="@mipmap/ic_launcher"
android:label="@string/permission_run_command_label"
android:protectionLevel="dangerous" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.DUMP" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backupscheme"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:name=".app.TermuxApplication"
android:allowBackup="false"
android:banner="@drawable/banner"
android:extractNativeLibs="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/application_name"
android:theme="@style/Theme.Termux"
android:supportsRtl="false" >
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="false"
android:theme="@style/Theme.Termux">
<!--
This (or rather, value 2.1 or higher) is needed to make the Samsung Galaxy S8
mark the app with "This app is optimized to run in full screen."
-->
<meta-data
android:name="android.max_aspect"
android:value="10.0" />
<activity
android:name="com.termux.app.TermuxActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:name=".app.TermuxActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|uiMode|keyboard|keyboardHidden|navigation"
android:label="@string/application_name"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" >
android:resizeableActivity="true"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" />
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity
android:name="com.termux.app.TermuxHelpActivity"
android:exported="false"
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
android:parentActivityName=".app.TermuxActivity"
android:label="@string/application_name" />
<activity-alias
android:name=".HomeActivity"
android:targetActivity=".app.TermuxActivity">
<!-- Launch activity automatically on boot on Android Things devices -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.IOT_LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity-alias>
<activity
android:name="com.termux.filepicker.TermuxFileReceiverActivity"
android:name=".app.activities.HelpActivity"
android:exported="false"
android:label="@string/application_name"
android:taskAffinity="com.termux.filereceiver"
android:parentActivityName=".app.TermuxActivity"
android:resizeableActivity="true"
android:theme="@android:style/Theme.Material.Light.DarkActionBar" />
<activity
android:name=".app.activities.SettingsActivity"
android:exported="true"
android:label="@string/title_activity_termux_settings"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar" />
<activity
android:name=".app.activities.ReportActivity"
android:theme="@style/Theme.AppCompat.TermuxReportActivity"
android:documentLaunchMode="intoExisting"
/>
<activity
android:name=".filepicker.TermuxFileReceiverActivity"
android:excludeFromRecents="true"
android:noHistory="true">
android:label="@string/application_name"
android:noHistory="true"
android:resizeableActivity="true"
android:taskAffinity="${TERMUX_PACKAGE_NAME}.filereceiver">
<!-- Accept multiple file types when sending. -->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/*" />
<data android:mimeType="audio/*" />
<data android:mimeType="image/*" />
@@ -63,23 +130,25 @@
<data android:mimeType="text/*" />
<data android:mimeType="video/*" />
</intent-filter>
<!-- Be more restrictive for viewing files, restricting ourselves to text files. -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<!-- Accept multiple file types to let Termux be usable as generic file viewer. -->
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/*" />
<data android:mimeType="audio/*" />
<data android:mimeType="image/*" />
<data android:mimeType="text/*" />
<data android:mimeType="application/json" />
<data android:mimeType="application/*xml*" />
<data android:mimeType="application/*latex*" />
<data android:mimeType="application/javascript" />
<data android:mimeType="video/*" />
</intent-filter>
</activity>
<provider
android:name=".filepicker.TermuxDocumentsProvider"
android:authorities="com.termux.documents"
android:grantUriPermissions="true"
android:authorities="${TERMUX_PACKAGE_NAME}.documents"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
@@ -87,9 +156,32 @@
</provider>
<service
android:name="com.termux.app.TermuxService"
android:name=".app.TermuxService"
android:exported="false" />
<service
android:name=".app.RunCommandService"
android:exported="true"
android:permission="${TERMUX_PACKAGE_NAME}.permission.RUN_COMMAND">
<intent-filter>
<action android:name="${TERMUX_PACKAGE_NAME}.RUN_COMMAND" />
</intent-filter>
</service>
<receiver android:name=".app.TermuxOpenReceiver" />
<provider
android:name=".app.TermuxOpenReceiver$ContentProvider"
android:authorities="${TERMUX_PACKAGE_NAME}.files"
android:exported="true"
android:grantUriPermissions="true"
android:readPermission="android.permission.permRead" />
<meta-data
android:name="com.sec.android.support.multiwindow"
android:value="true" />
<meta-data
android:name="com.samsung.android.multidisplay.keep_process_alive"
android:value="true" />
</application>
</manifest>

View File

@@ -0,0 +1,5 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libtermux-bootstrap
LOCAL_SRC_FILES := termux-bootstrap-zip.S termux-bootstrap.c
include $(BUILD_SHARED_LIBRARY)

View File

@@ -0,0 +1,18 @@
.global blob
.global blob_size
.section .rodata
blob:
#if defined __i686__
.incbin "bootstrap-i686.zip"
#elif defined __x86_64__
.incbin "bootstrap-x86_64.zip"
#elif defined __aarch64__
.incbin "bootstrap-aarch64.zip"
#elif defined __arm__
.incbin "bootstrap-arm.zip"
#else
# error Unsupported arch
#endif
1:
blob_size:
.int 1b - blob

View File

@@ -0,0 +1,11 @@
#include <jni.h>
extern jbyte blob[];
extern int blob_size;
JNIEXPORT jbyteArray JNICALL Java_com_termux_app_TermuxInstaller_getZip(JNIEnv *env, __attribute__((__unused__)) jobject This)
{
jbyteArray ret = (*env)->NewByteArray(env, blob_size);
(*env)->SetByteArrayRegion(env, ret, 0, blob_size, blob);
return ret;
}

View File

@@ -1,193 +0,0 @@
package com.termux.app;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* A background job launched by Termux.
*/
public final class BackgroundJob {
private static final String LOG_TAG = "termux-task";
final Process mProcess;
public BackgroundJob(String cwd, String fileToExecute, final String[] args, final TermuxService service) {
String[] env = buildEnvironment(false, cwd);
if (cwd == null) cwd = TermuxService.HOME_PATH;
final String[] progArray = setupProcessArgs(fileToExecute, args);
final String processDescription = Arrays.toString(progArray);
Process process;
try {
process = Runtime.getRuntime().exec(progArray, env, new File(cwd));
} catch (IOException e) {
mProcess = null;
// TODO: Visible error message?
Log.e(LOG_TAG, "Failed running background job: " + processDescription, e);
return;
}
mProcess = process;
final int pid = getPid(mProcess);
new Thread() {
@Override
public void run() {
Log.i(LOG_TAG, "[" + pid + "] starting: " + processDescription);
InputStream stdout = mProcess.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));
String line;
try {
// FIXME: Long lines.
while ((line = reader.readLine()) != null) {
Log.i(LOG_TAG, "[" + pid + "] stdout: " + line);
}
} catch (IOException e) {
Log.e(LOG_TAG, "Error reading output", e);
}
try {
int exitCode = mProcess.waitFor();
service.onBackgroundJobExited(BackgroundJob.this);
if (exitCode == 0) {
Log.i(LOG_TAG, "[" + pid + "] exited normally");
} else {
Log.w(LOG_TAG, "[" + pid + "] exited with code: " + exitCode);
}
} catch (InterruptedException e) {
// Ignore.
}
}
}.start();
new Thread() {
@Override
public void run() {
InputStream stderr = mProcess.getErrorStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stderr, StandardCharsets.UTF_8));
String line;
try {
// FIXME: Long lines.
while ((line = reader.readLine()) != null) {
Log.i(LOG_TAG, "[" + pid + "] stderr: " + line);
}
} catch (IOException e) {
// Ignore.
}
}
};
}
public static String[] buildEnvironment(boolean failSafe, String cwd) {
new File(TermuxService.HOME_PATH).mkdirs();
if (cwd == null) cwd = TermuxService.HOME_PATH;
final String termEnv = "TERM=xterm-256color";
final String homeEnv = "HOME=" + TermuxService.HOME_PATH;
final String prefixEnv = "PREFIX=" + TermuxService.PREFIX_PATH;
final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT");
final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA");
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE");
if (failSafe) {
// Keep the default path so that system binaries can be used in the failsafe session.
final String pathEnv = "PATH=" + System.getenv("PATH");
return new String[]{termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv};
} else {
final String ps1Env = "PS1=$ ";
final String ldEnv = "LD_LIBRARY_PATH=" + TermuxService.PREFIX_PATH + "/lib";
final String langEnv = "LANG=en_US.UTF-8";
final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets";
final String pwdEnv = "PWD=" + cwd;
return new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv};
}
}
public static int getPid(Process p) {
try {
Field f = p.getClass().getDeclaredField("pid");
f.setAccessible(true);
try {
return f.getInt(p);
} finally {
f.setAccessible(false);
}
} catch (Throwable e) {
return -1;
}
}
static String[] setupProcessArgs(String fileToExecute, String[] args) {
// The file to execute may either be:
// - An elf file, in which we execute it directly.
// - A script file without shebang, which we execute with our standard shell $PREFIX/bin/sh instead of the
// system /system/bin/sh. The system shell may vary and may not work at all due to LD_LIBRARY_PATH.
// - A file with shebang, which we try to handle with e.g. /bin/foo -> $PREFIX/bin/foo.
String interpreter = null;
try {
File file = new File(fileToExecute);
try (FileInputStream in = new FileInputStream(file)) {
byte[] buffer = new byte[256];
int bytesRead = in.read(buffer);
if (bytesRead > 4) {
if (buffer[0] == 0x7F && buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'F') {
// Elf file, do nothing.
} else if (buffer[0] == '#' && buffer[1] == '!') {
// Try to parse shebang.
StringBuilder builder = new StringBuilder();
for (int i = 2; i < bytesRead; i++) {
char c = (char) buffer[i];
if (c == ' ' || c == '\n') {
if (builder.length() == 0) {
// Skip whitespace after shebang.
continue;
} else {
// End of shebang.
String executable = builder.toString();
if (executable.startsWith("/usr") || executable.startsWith("/bin")) {
String[] parts = executable.split("/");
String binary = parts[parts.length - 1];
interpreter = TermuxService.PREFIX_PATH + "/bin/" + binary;
}
break;
}
} else {
builder.append(c);
}
}
} else {
// No shebang and no ELF, use standard shell.
interpreter = TermuxService.PREFIX_PATH + "/bin/sh";
}
}
}
} catch (IOException e) {
// Ignore.
}
List<String> result = new ArrayList<>();
if (interpreter != null) result.add(interpreter);
result.add(fileToExecute);
if (args != null) Collections.addAll(result, args);
return result.toArray(new String[result.size()]);
}
}

View File

@@ -1,177 +0,0 @@
package com.termux.app;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.GridLayout;
import android.widget.ToggleButton;
import com.termux.R;
import com.termux.terminal.TerminalSession;
import com.termux.view.TerminalView;
/**
* A view showing extra keys (such as Escape, Ctrl, Alt) not normally available on an Android soft
* keyboard.
*/
public final class ExtraKeysView extends GridLayout {
private static final int TEXT_COLOR = 0xFFFFFFFF;
public ExtraKeysView(Context context, AttributeSet attrs) {
super(context, attrs);
reload();
}
static void sendKey(View view, String keyName) {
int keyCode = 0;
String chars = null;
switch (keyName) {
case "ESC":
keyCode = KeyEvent.KEYCODE_ESCAPE;
break;
case "TAB":
keyCode = KeyEvent.KEYCODE_TAB;
break;
case "":
keyCode = KeyEvent.KEYCODE_DPAD_UP;
break;
case "":
keyCode = KeyEvent.KEYCODE_DPAD_LEFT;
break;
case "":
keyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
break;
case "":
keyCode = KeyEvent.KEYCODE_DPAD_DOWN;
break;
case "":
chars = "-";
break;
default:
chars = keyName;
}
if (keyCode > 0) {
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
} else {
TerminalView terminalView = (TerminalView) view.findViewById(R.id.terminal_view);
TerminalSession session = terminalView.getCurrentSession();
if (session != null) session.write(chars);
}
}
private ToggleButton controlButton;
private ToggleButton altButton;
private ToggleButton fnButton;
public boolean readControlButton() {
if (controlButton.isPressed()) return true;
boolean result = controlButton.isChecked();
if (result) {
controlButton.setChecked(false);
controlButton.setTextColor(TEXT_COLOR);
}
return result;
}
public boolean readAltButton() {
if (altButton.isPressed()) return true;
boolean result = altButton.isChecked();
if (result) {
altButton.setChecked(false);
altButton.setTextColor(TEXT_COLOR);
}
return result;
}
public boolean readFnButton() {
if (fnButton.isPressed()) return true;
boolean result = fnButton.isChecked();
if (result) {
fnButton.setChecked(false);
fnButton.setTextColor(TEXT_COLOR);
}
return result;
}
void reload() {
altButton = controlButton = null;
removeAllViews();
String[][] buttons = {
{"ESC", "CTRL", "ALT", "TAB", "", "/", "|"}
};
final int rows = buttons.length;
final int cols = buttons[0].length;
setRowCount(rows);
setColumnCount(cols);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
final String buttonText = buttons[row][col];
Button button;
switch (buttonText) {
case "CTRL":
button = controlButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
button.setClickable(true);
break;
case "ALT":
button = altButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
button.setClickable(true);
break;
case "FN":
button = fnButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
button.setClickable(true);
break;
default:
button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
break;
}
button.setText(buttonText);
button.setTextColor(TEXT_COLOR);
final Button finalButton = button;
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
View root = getRootView();
switch (buttonText) {
case "CTRL":
case "ALT":
case "FN":
ToggleButton self = (ToggleButton) finalButton;
self.setChecked(self.isChecked());
self.setTextColor(self.isChecked() ? 0xFF80DEEA : TEXT_COLOR);
break;
default:
sendKey(root, buttonText);
break;
}
}
});
GridLayout.LayoutParams param = new GridLayout.LayoutParams();
param.height = param.width = 0;
param.rightMargin = param.topMargin = 0;
param.setGravity(Gravity.LEFT);
float weight = "▲▼◀▶".contains(buttonText) ? 0.7f : 1.f;
param.columnSpec = GridLayout.spec(col, GridLayout.FILL, weight);
param.rowSpec = GridLayout.spec(row, GridLayout.FILL, 1.f);
button.setLayoutParams(param);
addView(button);
}
}
}
}

View File

@@ -1,67 +0,0 @@
package com.termux.app;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.view.View;
import com.termux.R;
/**
* Utility to manage full screen immersive mode.
* <p/>
* See https://code.google.com/p/android/issues/detail?id=5497
*/
final class FullScreenHelper {
private boolean mEnabled = false;
final TermuxActivity mActivity;
public FullScreenHelper(TermuxActivity activity) {
this.mActivity = activity;
}
public void setImmersive(boolean enabled) {
if (enabled == mEnabled) return;
mEnabled = enabled;
View decorView = mActivity.getWindow().getDecorView();
if (enabled) {
decorView.setOnSystemUiVisibilityChangeListener
(new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
if (mActivity.mSettings.isShowExtraKeys()) {
mActivity.findViewById(R.id.viewpager).setVisibility(View.VISIBLE);
}
setImmersiveMode();
} else {
mActivity.findViewById(R.id.viewpager).setVisibility(View.GONE);
}
}
});
setImmersiveMode();
} else {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
decorView.setOnSystemUiVisibilityChangeListener(null);
}
}
private static boolean isColorLight(int color) {
double darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
return darkness < 0.5;
}
void setImmersiveMode() {
int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_FULLSCREEN;
int color = ((ColorDrawable) mActivity.getWindow().getDecorView().getBackground()).getColor();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isColorLight(color))
flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
mActivity.getWindow().getDecorView().setSystemUiVisibility(flags);
}
}

View File

@@ -0,0 +1,219 @@
package com.termux.app;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import com.termux.R;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE;
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_SERVICE;
import com.termux.shared.file.FileUtils;
import com.termux.shared.logger.Logger;
import com.termux.shared.notification.NotificationUtils;
import com.termux.app.utils.PluginUtils;
import com.termux.shared.data.DataUtils;
import com.termux.shared.models.ExecutionCommand;
/**
* A service that receives {@link RUN_COMMAND_SERVICE#ACTION_RUN_COMMAND} intent from third party apps and
* plugins that contains info on command execution and forwards the extras to {@link TermuxService}
* for the actual execution.
*
* Check https://github.com/termux/termux-app/wiki/RUN_COMMAND-Intent for more info.
*/
public class RunCommandService extends Service {
private static final String LOG_TAG = "RunCommandService";
class LocalBinder extends Binder {
public final RunCommandService service = RunCommandService.this;
}
private final IBinder mBinder = new RunCommandService.LocalBinder();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onCreate() {
Logger.logVerbose(LOG_TAG, "onCreate");
runStartForeground();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Logger.logDebug(LOG_TAG, "onStartCommand");
if (intent == null) return Service.START_NOT_STICKY;
// Run again in case service is already started and onCreate() is not called
runStartForeground();
ExecutionCommand executionCommand = new ExecutionCommand();
executionCommand.pluginAPIHelp = this.getString(R.string.error_run_command_service_api_help, RUN_COMMAND_SERVICE.RUN_COMMAND_API_HELP_URL);
String errmsg;
// If invalid action passed, then just return
if (!RUN_COMMAND_SERVICE.ACTION_RUN_COMMAND.equals(intent.getAction())) {
errmsg = this.getString(R.string.error_run_command_service_invalid_intent_action, intent.getAction());
executionCommand.setStateFailed(ExecutionCommand.RESULT_CODE_FAILED, errmsg, null);
PluginUtils.processPluginExecutionCommandError(this, LOG_TAG, executionCommand, false);
return Service.START_NOT_STICKY;
}
executionCommand.executable = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_COMMAND_PATH);
executionCommand.arguments = intent.getStringArrayExtra(RUN_COMMAND_SERVICE.EXTRA_ARGUMENTS);
executionCommand.stdin = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_STDIN);
executionCommand.workingDirectory = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_WORKDIR);
executionCommand.inBackground = intent.getBooleanExtra(RUN_COMMAND_SERVICE.EXTRA_BACKGROUND, false);
executionCommand.sessionAction = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_SESSION_ACTION);
executionCommand.commandLabel = DataUtils.getDefaultIfNull(intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_COMMAND_LABEL), "RUN_COMMAND Execution Intent Command");
executionCommand.commandDescription = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_COMMAND_DESCRIPTION);
executionCommand.commandHelp = intent.getStringExtra(RUN_COMMAND_SERVICE.EXTRA_COMMAND_HELP);
executionCommand.isPluginExecutionCommand = true;
executionCommand.pluginPendingIntent = intent.getParcelableExtra(RUN_COMMAND_SERVICE.EXTRA_PENDING_INTENT);
// If "allow-external-apps" property to not set to "true", then just return
// We enable force notifications if "allow-external-apps" policy is violated so that the
// user knows someone tried to run a command in termux context, since it may be malicious
// app or imported (tasker) plugin project and not the user himself. If a pending intent is
// also sent, then its creator is also logged and shown.
errmsg = PluginUtils.checkIfRunCommandServiceAllowExternalAppsPolicyIsViolated(this);
if (errmsg != null) {
executionCommand.setStateFailed(ExecutionCommand.RESULT_CODE_FAILED, errmsg, null);
PluginUtils.processPluginExecutionCommandError(this, LOG_TAG, executionCommand, true);
return Service.START_NOT_STICKY;
}
// If executable is null or empty, then exit here instead of getting canonical path which would expand to "/"
if (executionCommand.executable == null || executionCommand.executable.isEmpty()) {
errmsg = this.getString(R.string.error_run_command_service_mandatory_extra_missing, RUN_COMMAND_SERVICE.EXTRA_COMMAND_PATH);
executionCommand.setStateFailed(ExecutionCommand.RESULT_CODE_FAILED, errmsg, null);
PluginUtils.processPluginExecutionCommandError(this, LOG_TAG, executionCommand, false);
return Service.START_NOT_STICKY;
}
// Get canonical path of executable
executionCommand.executable = FileUtils.getCanonicalPath(executionCommand.executable, null, true);
// If executable is not a regular file, or is not readable or executable, then just return
// Setting of missing read and execute permissions is not done
errmsg = FileUtils.validateRegularFileExistenceAndPermissions(this, "executable", executionCommand.executable, null,
PluginUtils.PLUGIN_EXECUTABLE_FILE_PERMISSIONS, true, true,
false);
if (errmsg != null) {
errmsg += "\n" + this.getString(R.string.msg_executable_absolute_path, executionCommand.executable);
executionCommand.setStateFailed(ExecutionCommand.RESULT_CODE_FAILED, errmsg, null);
PluginUtils.processPluginExecutionCommandError(this, LOG_TAG, executionCommand, false);
return Service.START_NOT_STICKY;
}
// If workingDirectory is not null or empty
if (executionCommand.workingDirectory != null && !executionCommand.workingDirectory.isEmpty()) {
// Get canonical path of workingDirectory
executionCommand.workingDirectory = FileUtils.getCanonicalPath(executionCommand.workingDirectory, null, true);
// If workingDirectory is not a directory, or is not readable or writable, then just return
// Creation of missing directory and setting of read, write and execute permissions are only done if workingDirectory is
// under {@link TermuxConstants#TERMUX_FILES_DIR_PATH}
// We try to set execute permissions, but ignore if they are missing, since only read and write permissions are required
// for working directories.
errmsg = FileUtils.validateDirectoryFileExistenceAndPermissions(this, "working", executionCommand.workingDirectory, TermuxConstants.TERMUX_FILES_DIR_PATH, true,
PluginUtils.PLUGIN_WORKING_DIRECTORY_PERMISSIONS, true, true,
true, true);
if (errmsg != null) {
errmsg += "\n" + this.getString(R.string.msg_working_directory_absolute_path, executionCommand.workingDirectory);
executionCommand.setStateFailed(ExecutionCommand.RESULT_CODE_FAILED, errmsg, null);
PluginUtils.processPluginExecutionCommandError(this, LOG_TAG, executionCommand, false);
return Service.START_NOT_STICKY;
}
}
executionCommand.executableUri = new Uri.Builder().scheme(TERMUX_SERVICE.URI_SCHEME_SERVICE_EXECUTE).path(FileUtils.getExpandedTermuxPath(executionCommand.executable)).build();
Logger.logVerbose(LOG_TAG, executionCommand.toString());
// Create execution intent with the action TERMUX_SERVICE#ACTION_SERVICE_EXECUTE to be sent to the TERMUX_SERVICE
Intent execIntent = new Intent(TERMUX_SERVICE.ACTION_SERVICE_EXECUTE, executionCommand.executableUri);
execIntent.setClass(this, TermuxService.class);
execIntent.putExtra(TERMUX_SERVICE.EXTRA_ARGUMENTS, executionCommand.arguments);
execIntent.putExtra(TERMUX_SERVICE.EXTRA_STDIN, executionCommand.stdin);
if (executionCommand.workingDirectory != null && !executionCommand.workingDirectory.isEmpty()) execIntent.putExtra(TERMUX_SERVICE.EXTRA_WORKDIR, executionCommand.workingDirectory);
execIntent.putExtra(TERMUX_SERVICE.EXTRA_BACKGROUND, executionCommand.inBackground);
execIntent.putExtra(TERMUX_SERVICE.EXTRA_SESSION_ACTION, executionCommand.sessionAction);
execIntent.putExtra(TERMUX_SERVICE.EXTRA_COMMAND_LABEL, executionCommand.commandLabel);
execIntent.putExtra(TERMUX_SERVICE.EXTRA_COMMAND_DESCRIPTION, executionCommand.commandDescription);
execIntent.putExtra(TERMUX_SERVICE.EXTRA_COMMAND_HELP, executionCommand.commandHelp);
execIntent.putExtra(TERMUX_SERVICE.EXTRA_PLUGIN_API_HELP, executionCommand.pluginAPIHelp);
execIntent.putExtra(TERMUX_SERVICE.EXTRA_PENDING_INTENT, executionCommand.pluginPendingIntent);
// Start TERMUX_SERVICE and pass it execution intent
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.startForegroundService(execIntent);
} else {
this.startService(execIntent);
}
runStopForeground();
return Service.START_NOT_STICKY;
}
private void runStartForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setupNotificationChannel();
startForeground(TermuxConstants.TERMUX_RUN_COMMAND_NOTIFICATION_ID, buildNotification());
}
}
private void runStopForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true);
}
}
private Notification buildNotification() {
// Build the notification
Notification.Builder builder = NotificationUtils.geNotificationBuilder(this,
TermuxConstants.TERMUX_RUN_COMMAND_NOTIFICATION_CHANNEL_ID, Notification.PRIORITY_LOW,
TermuxConstants.TERMUX_RUN_COMMAND_NOTIFICATION_CHANNEL_NAME, null, null,
null, NotificationUtils.NOTIFICATION_MODE_SILENT);
if (builder == null) return null;
// No need to show a timestamp:
builder.setShowWhen(false);
// Set notification icon
builder.setSmallIcon(R.drawable.ic_service_notification);
// Set background color for small notification icon
builder.setColor(0xFF607D8B);
return builder.build();
}
private void setupNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
NotificationUtils.setupNotificationChannel(this, TermuxConstants.TERMUX_RUN_COMMAND_NOTIFICATION_CHANNEL_ID,
TermuxConstants.TERMUX_RUN_COMMAND_NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
package com.termux.app;
import android.app.Application;
import com.termux.shared.crash.CrashHandler;
import com.termux.shared.settings.preferences.TermuxAppSharedPreferences;
import com.termux.shared.logger.Logger;
public class TermuxApplication extends Application {
public void onCreate() {
super.onCreate();
// Set crash handler for the app
CrashHandler.setCrashHandler(this);
// Set log level for the app
setLogLevel();
}
private void setLogLevel() {
// Load the log level from shared preferences and set it to the {@link Logger.CURRENT_LOG_LEVEL}
TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(getApplicationContext());
preferences.setLogLevel(null, preferences.getLogLevel());
Logger.logDebug("Starting Application");
}
}

View File

@@ -4,28 +4,23 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.os.Build;
import android.os.Environment;
import android.os.UserManager;
import android.system.Os;
import android.util.Log;
import android.util.Pair;
import android.view.WindowManager;
import com.termux.R;
import com.termux.terminal.EmulatorDebug;
import com.termux.shared.file.FileUtils;
import com.termux.shared.logger.Logger;
import com.termux.shared.termux.TermuxConstants;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@@ -34,16 +29,16 @@ import java.util.zip.ZipInputStream;
* Install the Termux bootstrap packages if necessary by following the below steps:
* <p/>
* (1) If $PREFIX already exist, assume that it is correct and be done. Note that this relies on that we do not create a
* broken $PREFIX folder below.
* broken $PREFIX directory below.
* <p/>
* (2) A progress dialog is shown with "Installing..." message and a spinner.
* <p/>
* (3) A staging folder, $STAGING_PREFIX, is {@link #deleteFolder(File)} if left over from broken installation below.
* (3) A staging directory, $STAGING_PREFIX, is cleared if left over from broken installation below.
* <p/>
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}.
* (4) The zip file is loaded from a shared library.
* <p/>
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
* continously encountering zip file entries:
* continuously encountering zip file entries:
* <p/>
* (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup.
* <p/>
@@ -51,24 +46,23 @@ import java.util.zip.ZipInputStream;
*/
final class TermuxInstaller {
/** Performs setup if necessary. */
static void setupIfNeeded(final Activity activity, final Runnable whenDone) {
private static final String LOG_TAG = "TermuxInstaller";
/** Performs bootstrap setup if necessary. */
static void setupBootstrapIfNeeded(final Activity activity, final Runnable whenDone) {
// Termux can only be run as the primary user (device owner) since only that
// account has the expected file system paths. Verify that:
UserManager um = (UserManager) activity.getSystemService(Context.USER_SERVICE);
boolean isPrimaryUser = um.getSerialNumberForUser(android.os.Process.myUserHandle()) == 0;
if (!isPrimaryUser) {
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_not_primary_user_message)
.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
System.exit(0);
}
}).setPositiveButton(android.R.string.ok, null).show();
String bootstrapErrorMessage = activity.getString(R.string.bootstrap_error_not_primary_user_message, TermuxConstants.TERMUX_PREFIX_DIR_PATH);
Logger.logError(LOG_TAG, bootstrapErrorMessage);
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(bootstrapErrorMessage)
.setOnDismissListener(dialog -> System.exit(0)).setPositiveButton(android.R.string.ok, null).show();
return;
}
final File PREFIX_FILE = new File(TermuxService.PREFIX_PATH);
final File PREFIX_FILE = TermuxConstants.TERMUX_PREFIX_DIR;
if (PREFIX_FILE.isDirectory()) {
whenDone.run();
return;
@@ -79,18 +73,25 @@ final class TermuxInstaller {
@Override
public void run() {
try {
final String STAGING_PREFIX_PATH = TermuxService.FILES_PATH + "/usr-staging";
Logger.logInfo(LOG_TAG, "Installing " + TermuxConstants.TERMUX_APP_NAME + " bootstrap packages.");
String errmsg;
final String STAGING_PREFIX_PATH = TermuxConstants.TERMUX_STAGING_PREFIX_DIR_PATH;
final File STAGING_PREFIX_FILE = new File(STAGING_PREFIX_PATH);
if (STAGING_PREFIX_FILE.exists()) {
deleteFolder(STAGING_PREFIX_FILE);
errmsg = FileUtils.clearDirectory(activity, "prefix staging directory", STAGING_PREFIX_PATH);
if (errmsg != null) {
throw new RuntimeException(errmsg);
}
Logger.logInfo(LOG_TAG, "Extracting bootstrap zip to prefix staging directory \"" + TermuxConstants.TERMUX_STAGING_PREFIX_DIR_PATH + "\".");
final byte[] buffer = new byte[8096];
final List<Pair<String, String>> symlinks = new ArrayList<>(50);
final URL zipUrl = determineZipUrl();
try (ZipInputStream zipInput = new ZipInputStream(zipUrl.openStream())) {
final byte[] zipBytes = loadZipBytes();
try (ZipInputStream zipInput = new ZipInputStream(new ByteArrayInputStream(zipBytes))) {
ZipEntry zipEntry;
while ((zipEntry = zipInput.getNextEntry()) != null) {
if (zipEntry.getName().equals("SYMLINKS.txt")) {
@@ -103,14 +104,17 @@ final class TermuxInstaller {
String oldPath = parts[0];
String newPath = STAGING_PREFIX_PATH + "/" + parts[1];
symlinks.add(Pair.create(oldPath, newPath));
ensureDirectoryExists(activity, new File(newPath).getParentFile());
}
} else {
String zipEntryName = zipEntry.getName();
File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName);
if (zipEntry.isDirectory()) {
if (!targetFile.mkdirs())
throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath());
} else {
boolean isDirectory = zipEntry.isDirectory();
ensureDirectoryExists(activity, isDirectory ? targetFile : targetFile.getParentFile());
if (!isDirectory) {
try (FileOutputStream outStream = new FileOutputStream(targetFile)) {
int readBytes;
while ((readBytes = zipInput.read(buffer)) != -1)
@@ -131,50 +135,36 @@ final class TermuxInstaller {
Os.symlink(symlink.first, symlink.second);
}
Logger.logInfo(LOG_TAG, "Moving prefix staging to prefix directory.");
if (!STAGING_PREFIX_FILE.renameTo(PREFIX_FILE)) {
throw new RuntimeException("Unable to rename staging folder");
throw new RuntimeException("Moving prefix staging to prefix directory failed");
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
whenDone.run();
}
});
Logger.logInfo(LOG_TAG, "Bootstrap packages installed successfully.");
activity.runOnUiThread(whenDone);
} catch (final Exception e) {
Log.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
.setNegativeButton(R.string.bootstrap_error_abort, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
activity.finish();
}
}).setPositiveButton(R.string.bootstrap_error_try_again, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
TermuxInstaller.setupIfNeeded(activity, whenDone);
}
}).show();
} catch (WindowManager.BadTokenException e) {
// Activity already dismissed - ignore.
}
Logger.logStackTraceWithMessage(LOG_TAG, "Bootstrap error", e);
activity.runOnUiThread(() -> {
try {
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
.setNegativeButton(R.string.bootstrap_error_abort, (dialog, which) -> {
dialog.dismiss();
activity.finish();
}).setPositiveButton(R.string.bootstrap_error_try_again, (dialog, which) -> {
dialog.dismiss();
TermuxInstaller.setupBootstrapIfNeeded(activity, whenDone);
}).show();
} catch (WindowManager.BadTokenException e1) {
// Activity already dismissed - ignore.
}
});
} finally {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
progress.dismiss();
} catch (RuntimeException e) {
// Activity already dismissed - ignore.
}
activity.runOnUiThread(() -> {
try {
progress.dismiss();
} catch (RuntimeException e) {
// Activity already dismissed - ignore.
}
});
}
@@ -182,61 +172,24 @@ final class TermuxInstaller {
}.start();
}
/** Get bootstrap zip url for this systems cpu architecture. */
static URL determineZipUrl() throws MalformedURLException {
String archName = determineTermuxArchName();
return new URL("https://termux.net/bootstrap/bootstrap-" + archName + ".zip");
}
private static String determineTermuxArchName() {
// Note that we cannot use System.getProperty("os.arch") since that may give e.g. "aarch64"
// while a 64-bit runtime may not be installed (like on the Samsung Galaxy S5 Neo).
// Instead we search through the supported abi:s on the device, see:
// http://developer.android.com/ndk/guides/abis.html
// Note that we search for abi:s in preferred order (the ordering of the
// Build.SUPPORTED_ABIS list) to avoid e.g. installing arm on an x86 system where arm
// emulation is available.
for (String androidArch : Build.SUPPORTED_ABIS) {
switch (androidArch) {
case "arm64-v8a": return "aarch64";
case "armeabi-v7a": return "arm";
case "x86_64": return "x86_64";
case "x86": return "i686";
}
}
throw new RuntimeException("Unable to determine arch from Build.SUPPORTED_ABIS = " +
Arrays.toString(Build.SUPPORTED_ABIS));
}
/** Delete a folder and all its content or throw. */
static void deleteFolder(File fileOrDirectory) {
File[] children = fileOrDirectory.listFiles();
if (children != null) {
for (File child : children) {
deleteFolder(child);
}
}
if (!fileOrDirectory.delete()) {
throw new RuntimeException("Unable to delete " + (fileOrDirectory.isDirectory() ? "directory " : "file ") + fileOrDirectory.getAbsolutePath());
}
}
public static void setupStorageSymlinks(final Context context) {
static void setupStorageSymlinks(final Context context) {
final String LOG_TAG = "termux-storage";
Logger.logInfo(LOG_TAG, "Setting up storage symlinks.");
new Thread() {
public void run() {
try {
File storageDir = new File(TermuxService.HOME_PATH, "storage");
String errmsg;
File storageDir = TermuxConstants.TERMUX_STORAGE_HOME_DIR;
if (storageDir.exists() && !storageDir.delete()) {
Log.e(LOG_TAG, "Could not delete old $HOME/storage");
errmsg = FileUtils.clearDirectory(context, "~/storage", storageDir.getAbsolutePath());
if (errmsg != null) {
Logger.logErrorAndShowToast(context, LOG_TAG, errmsg);
return;
}
if (!storageDir.mkdirs()) {
Log.e(LOG_TAG, "Unable to mkdirs() for $HOME/storage");
return;
}
Logger.logInfo(LOG_TAG, "Setting up storage symlinks at ~/storage/shared, ~/storage/downloads, ~/storage/dcim, ~/storage/pictures, ~/storage/music and ~/storage/movies for directories in \"" + Environment.getExternalStorageDirectory().getAbsolutePath() + "\".");
File sharedDir = Environment.getExternalStorageDirectory();
Os.symlink(sharedDir.getAbsolutePath(), new File(storageDir, "shared").getAbsolutePath());
@@ -262,14 +215,34 @@ final class TermuxInstaller {
File dir = dirs[i];
if (dir == null) continue;
String symlinkName = "external-" + i;
Logger.logInfo(LOG_TAG, "Setting up storage symlinks at ~/storage/" + symlinkName + " for \"" + dir.getAbsolutePath() + "\".");
Os.symlink(dir.getAbsolutePath(), new File(storageDir, symlinkName).getAbsolutePath());
}
}
Logger.logInfo(LOG_TAG, "Storage symlinks created successfully.");
} catch (Exception e) {
Log.e(LOG_TAG, "Error setting up link", e);
Logger.logStackTraceWithMessage(LOG_TAG, "Error setting up link", e);
}
}
}.start();
}
private static void ensureDirectoryExists(Context context, File directory) {
String errmsg;
errmsg = FileUtils.createDirectoryFile(context, directory.getAbsolutePath());
if (errmsg != null) {
throw new RuntimeException(errmsg);
}
}
public static byte[] loadZipBytes() {
// Only load the shared library when necessary to save memory usage.
System.loadLibrary("termux-bootstrap");
return getZip();
}
public static native byte[] getZip();
}

View File

@@ -1,276 +0,0 @@
package com.termux.app;
import android.content.Context;
import android.media.AudioManager;
import android.support.v4.widget.DrawerLayout;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.InputMethodManager;
import com.termux.terminal.KeyHandler;
import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession;
import com.termux.view.TerminalKeyListener;
import java.util.List;
public final class TermuxKeyListener implements TerminalKeyListener {
final TermuxActivity mActivity;
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
public TermuxKeyListener(TermuxActivity activity) {
this.mActivity = activity;
}
@Override
public float onScale(float scale) {
if (scale < 0.9f || scale > 1.1f) {
boolean increase = scale > 1.f;
mActivity.changeFontSize(increase);
return 1.0f;
}
return scale;
}
@Override
public void onSingleTapUp(MotionEvent e) {
InputMethodManager mgr = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
mgr.showSoftInput(mActivity.mTerminalView, InputMethodManager.SHOW_IMPLICIT);
}
@Override
public boolean shouldBackButtonBeMappedToEscape() {
return mActivity.mSettings.mBackIsEscape;
}
@Override
public void copyModeChanged(boolean copyMode) {
// Disable drawer while copying.
mActivity.getDrawer().setDrawerLockMode(copyMode ? DrawerLayout.LOCK_MODE_LOCKED_CLOSED : DrawerLayout.LOCK_MODE_UNLOCKED);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession currentSession) {
if (handleVirtualKeys(keyCode, e, true)) return true;
if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) {
mActivity.removeFinishedSession(currentSession);
return true;
} else if (e.isCtrlPressed() && e.isAltPressed()) {
// Get the unmodified code point:
int unicodeChar = e.getUnicodeChar(0);
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || unicodeChar == 'n'/* next */) {
mActivity.switchToSession(true);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP || unicodeChar == 'p' /* previous */) {
mActivity.switchToSession(false);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
mActivity.getDrawer().openDrawer(Gravity.LEFT);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
mActivity.getDrawer().closeDrawers();
} else if (unicodeChar == 'f'/* full screen */) {
mActivity.toggleImmersive();
} else if (unicodeChar == 'k'/* keyboard */) {
InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
} else if (unicodeChar == 'm'/* menu */) {
mActivity.mTerminalView.showContextMenu();
} else if (unicodeChar == 'r'/* rename */) {
mActivity.renameSession(currentSession);
} else if (unicodeChar == 'c'/* create */) {
mActivity.addNewSession(false, null);
} else if (unicodeChar == 'u' /* urls */) {
mActivity.showUrlSelection();
} else if (unicodeChar == 'v') {
mActivity.doPaste();
} else if (unicodeChar == '+' || e.getUnicodeChar(KeyEvent.META_SHIFT_ON) == '+') {
// We also check for the shifted char here since shift may be required to produce '+',
// see https://github.com/termux/termux-api/issues/2
mActivity.changeFontSize(true);
} else if (unicodeChar == '-') {
mActivity.changeFontSize(false);
} else if (unicodeChar >= '1' && unicodeChar <= '9') {
int num = unicodeChar - '1';
TermuxService service = mActivity.mTermService;
if (service.getSessions().size() > num)
mActivity.switchToSession(service.getSessions().get(num));
}
return true;
}
return false;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent e) {
return handleVirtualKeys(keyCode, e, false);
}
@Override
public boolean readControlKey() {
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readControlButton()) || mVirtualControlKeyDown;
}
@Override
public boolean readAltKey() {
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readAltButton());
}
@Override
public boolean onCodePoint(final int codePoint, boolean ctrlDown, TerminalSession session) {
if (mVirtualFnKeyDown) {
int resultingKeyCode = -1;
int resultingCodePoint = -1;
boolean altDown = false;
int lowerCase = Character.toLowerCase(codePoint);
switch (lowerCase) {
// Arrow keys.
case 'w':
resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP;
break;
case 'a':
resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT;
break;
case 's':
resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN;
break;
case 'd':
resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
break;
// Page up and down.
case 'p':
resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP;
break;
case 'n':
resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN;
break;
// Some special keys:
case 't':
resultingKeyCode = KeyEvent.KEYCODE_TAB;
break;
case 'i':
resultingKeyCode = KeyEvent.KEYCODE_INSERT;
break;
case 'h':
resultingCodePoint = '~';
break;
// Special characters to input.
case 'u':
resultingCodePoint = '_';
break;
case 'l':
resultingCodePoint = '|';
break;
// Function keys.
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
resultingKeyCode = (codePoint - '1') + KeyEvent.KEYCODE_F1;
break;
case '0':
resultingKeyCode = KeyEvent.KEYCODE_F10;
break;
// Other special keys.
case 'e':
resultingCodePoint = /*Escape*/ 27;
break;
case '.':
resultingCodePoint = /*^.*/ 28;
break;
case 'b': // alt+b, jumping backward in readline.
case 'f': // alf+f, jumping forward in readline.
case 'x': // alt+x, common in emacs.
resultingCodePoint = lowerCase;
altDown = true;
break;
// Volume control.
case 'v':
resultingCodePoint = -1;
AudioManager audio = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI);
break;
// Writing mode:
case 'q':
mActivity.toggleShowExtraKeys();
break;
}
if (resultingKeyCode != -1) {
TerminalEmulator term = session.getEmulator();
session.write(KeyHandler.getCode(resultingKeyCode, 0, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode()));
} else if (resultingCodePoint != -1) {
session.writeCodePoint(altDown, resultingCodePoint);
}
return true;
} else if (ctrlDown) {
if (codePoint == 106 /* Ctrl+j or \n */ && !session.isRunning()) {
mActivity.removeFinishedSession(session);
return true;
}
List<TermuxPreferences.KeyboardShortcut> shortcuts = mActivity.mSettings.shortcuts;
if (!shortcuts.isEmpty()) {
int codePointLowerCase = Character.toLowerCase(codePoint);
for (int i = shortcuts.size() - 1; i >= 0; i--) {
TermuxPreferences.KeyboardShortcut shortcut = shortcuts.get(i);
if (codePointLowerCase == shortcut.codePoint) {
switch (shortcut.shortcutAction) {
case TermuxPreferences.SHORTCUT_ACTION_CREATE_SESSION:
mActivity.addNewSession(false, null);
return true;
case TermuxPreferences.SHORTCUT_ACTION_PREVIOUS_SESSION:
mActivity.switchToSession(false);
return true;
case TermuxPreferences.SHORTCUT_ACTION_NEXT_SESSION:
mActivity.switchToSession(true);
return true;
case TermuxPreferences.SHORTCUT_ACTION_RENAME_SESSION:
mActivity.renameSession(mActivity.getCurrentTermSession());
return true;
}
}
}
}
}
return false;
}
/** Handle dedicated volume buttons as virtual keys if applicable. */
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
InputDevice inputDevice = event.getDevice();
if (inputDevice != null && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
// Do not steal dedicated buttons from a full external keyboard.
return false;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
mVirtualControlKeyDown = down;
return true;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
mVirtualFnKeyDown = down;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,193 @@
package com.termux.app;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.webkit.MimeTypeMap;
import com.termux.shared.logger.Logger;
import com.termux.shared.termux.TermuxConstants;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import androidx.annotation.NonNull;
public class TermuxOpenReceiver extends BroadcastReceiver {
private static final String LOG_TAG = "TermuxOpenReceiver";
@Override
public void onReceive(Context context, Intent intent) {
final Uri data = intent.getData();
if (data == null) {
Logger.logError(LOG_TAG, "termux-open: Called without intent data");
return;
}
final String filePath = data.getPath();
final String contentTypeExtra = intent.getStringExtra("content-type");
final boolean useChooser = intent.getBooleanExtra("chooser", false);
final String intentAction = intent.getAction() == null ? Intent.ACTION_VIEW : intent.getAction();
switch (intentAction) {
case Intent.ACTION_SEND:
case Intent.ACTION_VIEW:
// Ok.
break;
default:
Logger.logError(LOG_TAG, "Invalid action '" + intentAction + "', using 'view'");
break;
}
final boolean isExternalUrl = data.getScheme() != null && !data.getScheme().equals("file");
if (isExternalUrl) {
Intent urlIntent = new Intent(intentAction, data);
if (intentAction.equals(Intent.ACTION_SEND)) {
urlIntent.putExtra(Intent.EXTRA_TEXT, data.toString());
urlIntent.setData(null);
} else if (contentTypeExtra != null) {
urlIntent.setDataAndType(data, contentTypeExtra);
}
urlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(urlIntent);
} catch (ActivityNotFoundException e) {
Logger.logError(LOG_TAG, "termux-open: No app handles the url " + data);
}
return;
}
final File fileToShare = new File(filePath);
if (!(fileToShare.isFile() && fileToShare.canRead())) {
Logger.logError(LOG_TAG, "termux-open: Not a readable file: '" + fileToShare.getAbsolutePath() + "'");
return;
}
Intent sendIntent = new Intent();
sendIntent.setAction(intentAction);
sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
String contentTypeToUse;
if (contentTypeExtra == null) {
String fileName = fileToShare.getName();
int lastDotIndex = fileName.lastIndexOf('.');
String fileExtension = fileName.substring(lastDotIndex + 1);
MimeTypeMap mimeTypes = MimeTypeMap.getSingleton();
// Lower casing makes it work with e.g. "JPG":
contentTypeToUse = mimeTypes.getMimeTypeFromExtension(fileExtension.toLowerCase());
if (contentTypeToUse == null) contentTypeToUse = "application/octet-stream";
} else {
contentTypeToUse = contentTypeExtra;
}
Uri uriToShare = Uri.parse("content://" + TermuxConstants.TERMUX_FILE_SHARE_URI_AUTHORITY + fileToShare.getAbsolutePath());
if (Intent.ACTION_SEND.equals(intentAction)) {
sendIntent.putExtra(Intent.EXTRA_STREAM, uriToShare);
sendIntent.setType(contentTypeToUse);
} else {
sendIntent.setDataAndType(uriToShare, contentTypeToUse);
}
if (useChooser) {
sendIntent = Intent.createChooser(sendIntent, null).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
try {
context.startActivity(sendIntent);
} catch (ActivityNotFoundException e) {
Logger.logError(LOG_TAG, "termux-open: No app handles the url " + data);
}
}
public static class ContentProvider extends android.content.ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
File file = new File(uri.getPath());
if (projection == null) {
projection = new String[]{
MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.SIZE,
MediaStore.MediaColumns._ID
};
}
Object[] row = new Object[projection.length];
for (int i = 0; i < projection.length; i++) {
String column = projection[i];
Object value;
switch (column) {
case MediaStore.MediaColumns.DISPLAY_NAME:
value = file.getName();
break;
case MediaStore.MediaColumns.SIZE:
value = (int) file.length();
break;
case MediaStore.MediaColumns._ID:
value = 1;
break;
default:
value = null;
}
row[i] = value;
}
MatrixCursor cursor = new MatrixCursor(projection);
cursor.addRow(row);
return cursor;
}
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
File file = new File(uri.getPath());
try {
String path = file.getCanonicalPath();
String storagePath = Environment.getExternalStorageDirectory().getCanonicalPath();
// See https://support.google.com/faqs/answer/7496913:
if (!(path.startsWith(TermuxConstants.TERMUX_FILES_DIR_PATH) || path.startsWith(storagePath))) {
throw new IllegalArgumentException("Invalid path: " + path);
}
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
}
}

View File

@@ -1,207 +0,0 @@
package com.termux.app;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.IntDef;
import android.util.Log;
import android.util.TypedValue;
import android.widget.Toast;
import com.termux.terminal.TerminalSession;
import java.io.File;
import java.io.FileInputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
final class TermuxPreferences {
@IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
@Retention(RetentionPolicy.SOURCE)
public @interface AsciiBellBehaviour {
}
static final int BELL_VIBRATE = 1;
static final int BELL_BEEP = 2;
static final int BELL_IGNORE = 3;
private final int MIN_FONTSIZE;
private static final int MAX_FONTSIZE = 256;
private static final String FULLSCREEN_KEY = "fullscreen";
private static final String SHOW_EXTRA_KEYS_KEY = "show_extra_keys";
private static final String FONTSIZE_KEY = "fontsize";
private static final String CURRENT_SESSION_KEY = "current_session";
private static final String SHOW_WELCOME_DIALOG_KEY = "intro_dialog";
private boolean mFullScreen;
private int mFontSize;
@AsciiBellBehaviour
int mBellBehaviour = BELL_VIBRATE;
boolean mBackIsEscape;
boolean mShowExtraKeys;
TermuxPreferences(Context context) {
reloadFromProperties(context);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
// This is a bit arbitrary and sub-optimal. We want to give a sensible default for minimum font size
// to prevent invisible text due to zoom be mistake:
MIN_FONTSIZE = (int) (4f * dipInPixels);
mFullScreen = prefs.getBoolean(FULLSCREEN_KEY, false);
mShowExtraKeys = prefs.getBoolean(SHOW_EXTRA_KEYS_KEY, false);
// http://www.google.com/design/spec/style/typography.html#typography-line-height
int defaultFontSize = Math.round(12 * dipInPixels);
// Make it divisible by 2 since that is the minimal adjustment step:
if (defaultFontSize % 2 == 1) defaultFontSize--;
try {
mFontSize = Integer.parseInt(prefs.getString(FONTSIZE_KEY, Integer.toString(defaultFontSize)));
} catch (NumberFormatException | ClassCastException e) {
mFontSize = defaultFontSize;
}
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
}
boolean isFullScreen() {
return mFullScreen;
}
void setFullScreen(Context context, boolean newValue) {
mFullScreen = newValue;
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(FULLSCREEN_KEY, newValue).apply();
}
boolean isShowExtraKeys() {
return mShowExtraKeys;
}
boolean toggleShowExtraKeys(Context context) {
mShowExtraKeys = !mShowExtraKeys;
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_EXTRA_KEYS_KEY, mShowExtraKeys).apply();
return mShowExtraKeys;
}
int getFontSize() {
return mFontSize;
}
void changeFontSize(Context context, boolean increase) {
mFontSize += (increase ? 1 : -1) * 2;
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs.edit().putString(FONTSIZE_KEY, Integer.toString(mFontSize)).apply();
}
static void storeCurrentSession(Context context, TerminalSession session) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).apply();
}
static TerminalSession getCurrentSession(TermuxActivity context) {
String sessionHandle = PreferenceManager.getDefaultSharedPreferences(context).getString(TermuxPreferences.CURRENT_SESSION_KEY, "");
for (int i = 0, len = context.mTermService.getSessions().size(); i < len; i++) {
TerminalSession session = context.mTermService.getSessions().get(i);
if (session.mHandle.equals(sessionHandle)) return session;
}
return null;
}
public static boolean isShowWelcomeDialog(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SHOW_WELCOME_DIALOG_KEY, true);
}
public static void disableWelcomeDialog(Context context) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_WELCOME_DIALOG_KEY, false).apply();
}
public void reloadFromProperties(Context context) {
try {
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
if (!propsFile.exists())
propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties");
Properties props = new Properties();
if (propsFile.isFile() && propsFile.canRead()) {
try (FileInputStream in = new FileInputStream(propsFile)) {
props.load(in);
}
}
switch (props.getProperty("bell-character", "vibrate")) {
case "beep":
mBellBehaviour = BELL_BEEP;
break;
case "ignore":
mBellBehaviour = BELL_IGNORE;
break;
default: // "vibrate".
mBellBehaviour = BELL_VIBRATE;
break;
}
mBackIsEscape = "escape".equals(props.getProperty("back-key", "back"));
shortcuts.clear();
parseAction("shortcut.create-session", SHORTCUT_ACTION_CREATE_SESSION, props);
parseAction("shortcut.next-session", SHORTCUT_ACTION_NEXT_SESSION, props);
parseAction("shortcut.previous-session", SHORTCUT_ACTION_PREVIOUS_SESSION, props);
parseAction("shortcut.rename-session", SHORTCUT_ACTION_RENAME_SESSION, props);
} catch (Exception e) {
Toast.makeText(context, "Error loading properties: " + e.getMessage(), Toast.LENGTH_LONG).show();
Log.e("termux", "Error loading props", e);
}
}
public static final int SHORTCUT_ACTION_CREATE_SESSION = 1;
public static final int SHORTCUT_ACTION_NEXT_SESSION = 2;
public static final int SHORTCUT_ACTION_PREVIOUS_SESSION = 3;
public static final int SHORTCUT_ACTION_RENAME_SESSION = 4;
public final static class KeyboardShortcut {
public KeyboardShortcut(int codePoint, int shortcutAction) {
this.codePoint = codePoint;
this.shortcutAction = shortcutAction;
}
final int codePoint;
final int shortcutAction;
}
final List<KeyboardShortcut> shortcuts = new ArrayList<>();
private void parseAction(String name, int shortcutAction, Properties props) {
String value = props.getProperty(name);
if (value == null) return;
String[] parts = value.toLowerCase().trim().split("\\+");
String input = parts.length == 2 ? parts[1].trim() : null;
if (!(parts.length == 2 && parts[0].trim().equals("ctrl")) || input.isEmpty() || input.length() > 2) {
Log.e("termux", "Keyboard shortcut '" + name + "' is not Ctrl+<something>");
return;
}
char c = input.charAt(0);
int codePoint = c;
if (Character.isLowSurrogate(c)) {
if (input.length() != 2 || Character.isHighSurrogate(input.charAt(1))) {
Log.e("termux", "Keyboard shortcut '" + name + "' is not Ctrl+<something>");
return;
} else {
codePoint = Character.toCodePoint(input.charAt(1), c);
}
}
shortcuts.add(new KeyboardShortcut(codePoint, shortcutAction));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
package com.termux.app;
package com.termux.app.activities;
import android.app.Activity;
import android.content.ActivityNotFoundException;
@@ -13,7 +13,7 @@ import android.widget.ProgressBar;
import android.widget.RelativeLayout;
/** Basic embedded browser for viewing help pages. */
public final class TermuxHelpActivity extends Activity {
public final class HelpActivity extends Activity {
WebView mWebView;
@@ -39,7 +39,7 @@ public final class TermuxHelpActivity extends Activity {
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("https://termux.com")) {
if (url.startsWith("https://wiki.termux.com")) {
// Inline help.
setContentView(progressLayout);
return false;
@@ -60,7 +60,7 @@ public final class TermuxHelpActivity extends Activity {
setContentView(mWebView);
}
});
mWebView.loadUrl("https://termux.com/help.html");
mWebView.loadUrl("https://wiki.termux.com/wiki/Main_Page");
}
@Override

View File

@@ -0,0 +1,180 @@
package com.termux.app.activities;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import com.termux.R;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.markdown.MarkdownUtils;
import com.termux.shared.interact.ShareUtils;
import com.termux.app.models.ReportInfo;
import org.commonmark.node.FencedCodeBlock;
import io.noties.markwon.Markwon;
import io.noties.markwon.recycler.MarkwonAdapter;
import io.noties.markwon.recycler.SimpleEntry;
public class ReportActivity extends AppCompatActivity {
private static final String EXTRA_REPORT_INFO = "report_info";
ReportInfo mReportInfo;
String mReportMarkdownString;
String mReportActivityMarkdownString;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_report);
Toolbar toolbar = findViewById(R.id.toolbar);
if (toolbar != null) {
setSupportActionBar(toolbar);
}
Bundle bundle = null;
Intent intent = getIntent();
if (intent != null)
bundle = intent.getExtras();
else if (savedInstanceState != null)
bundle = savedInstanceState;
updateUI(bundle);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
if (intent != null)
updateUI(intent.getExtras());
}
private void updateUI(Bundle bundle) {
if (bundle == null) {
finish();
return;
}
mReportInfo = (ReportInfo) bundle.getSerializable(EXTRA_REPORT_INFO);
if (mReportInfo == null) {
finish();
return;
}
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
if (mReportInfo.reportTitle != null)
actionBar.setTitle(mReportInfo.reportTitle);
else
actionBar.setTitle(TermuxConstants.TERMUX_APP_NAME + " App Report");
}
RecyclerView recyclerView = findViewById(R.id.recycler_view);
final Markwon markwon = MarkdownUtils.getRecyclerMarkwonBuilder(this);
final MarkwonAdapter adapter = MarkwonAdapter.builderTextViewIsRoot(R.layout.activity_report_adapter_node_default)
.include(FencedCodeBlock.class, SimpleEntry.create(R.layout.activity_report_adapter_node_code_block, R.id.code_text_view))
.build();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
generateReportActivityMarkdownString();
adapter.setMarkdown(markwon, mReportActivityMarkdownString);
adapter.notifyDataSetChanged();
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(EXTRA_REPORT_INFO, mReportInfo);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_report, menu);
return true;
}
@Override
public void onBackPressed() {
// Remove activity from recents menu on back button press
finishAndRemoveTask();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
int id = item.getItemId();
if (id == R.id.menu_item_share_report) {
if (mReportMarkdownString != null)
ShareUtils.shareText(this, getString(R.string.title_report_text), mReportMarkdownString);
} else if (id == R.id.menu_item_copy_report) {
if (mReportMarkdownString != null)
ShareUtils.copyTextToClipboard(this, mReportMarkdownString, null);
}
return false;
}
/**
* Generate the markdown {@link String} to be shown in {@link ReportActivity}.
*/
private void generateReportActivityMarkdownString() {
mReportMarkdownString = ReportInfo.getReportInfoMarkdownString(mReportInfo);
mReportActivityMarkdownString = "";
if (mReportInfo.reportStringPrefix != null)
mReportActivityMarkdownString += mReportInfo.reportStringPrefix;
mReportActivityMarkdownString += mReportMarkdownString;
if (mReportInfo.reportStringSuffix != null)
mReportActivityMarkdownString += mReportInfo.reportStringSuffix;
}
public static void startReportActivity(@NonNull final Context context, @NonNull final ReportInfo reportInfo) {
context.startActivity(newInstance(context, reportInfo));
}
public static Intent newInstance(@NonNull final Context context, @NonNull final ReportInfo reportInfo) {
Intent intent = new Intent(context, ReportActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable(EXTRA_REPORT_INFO, reportInfo);
intent.putExtras(bundle);
// Note that ReportActivity task has documentLaunchMode="intoExisting" set in AndroidManifest.xml
// which has equivalent behaviour to the following. The following dynamic way doesn't seem to
// work for notification pending intent, i.e separate task isn't created and activity is
// launched in the same task as TermuxActivity.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
return intent;
}
}

View File

@@ -0,0 +1,43 @@
package com.termux.app.activities;
import android.os.Bundle;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceFragmentCompat;
import com.termux.R;
public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.settings, new RootPreferencesFragment())
.commit();
}
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
}
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
public static class RootPreferencesFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
}
}
}

View File

@@ -0,0 +1,138 @@
package com.termux.app.fragments.settings;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceDataStore;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import com.termux.R;
import com.termux.shared.settings.preferences.TermuxAppSharedPreferences;
import com.termux.shared.logger.Logger;
@Keep
public class DebuggingPreferencesFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
PreferenceManager preferenceManager = getPreferenceManager();
preferenceManager.setPreferenceDataStore(DebuggingPreferencesDataStore.getInstance(getContext()));
setPreferencesFromResource(R.xml.debugging_preferences, rootKey);
PreferenceCategory loggingCategory = findPreference("logging");
if (loggingCategory != null) {
final ListPreference logLevelListPreference = setLogLevelListPreferenceData(findPreference("log_level"), getActivity());
loggingCategory.addPreference(logLevelListPreference);
}
}
protected ListPreference setLogLevelListPreferenceData(ListPreference logLevelListPreference, Context context) {
if (logLevelListPreference == null)
logLevelListPreference = new ListPreference(context);
CharSequence[] logLevels = Logger.getLogLevelsArray();
CharSequence[] logLevelLabels = Logger.getLogLevelLabelsArray(context, logLevels, true);
logLevelListPreference.setEntryValues(logLevels);
logLevelListPreference.setEntries(logLevelLabels);
logLevelListPreference.setValue(String.valueOf(Logger.getLogLevel()));
logLevelListPreference.setDefaultValue(Logger.getLogLevel());
return logLevelListPreference;
}
}
class DebuggingPreferencesDataStore extends PreferenceDataStore {
private final Context mContext;
private final TermuxAppSharedPreferences mPreferences;
private static DebuggingPreferencesDataStore mInstance;
private DebuggingPreferencesDataStore(Context context) {
mContext = context;
mPreferences = new TermuxAppSharedPreferences(context);
}
public static synchronized DebuggingPreferencesDataStore getInstance(Context context) {
if (mInstance == null) {
mInstance = new DebuggingPreferencesDataStore(context.getApplicationContext());
}
return mInstance;
}
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
if (key == null) return null;
switch (key) {
case "log_level":
return String.valueOf(mPreferences.getLogLevel());
default:
return null;
}
}
@Override
public void putString(String key, @Nullable String value) {
if (key == null) return;
switch (key) {
case "log_level":
if (value != null) {
mPreferences.setLogLevel(mContext, Integer.parseInt(value));
}
break;
default:
break;
}
}
@Override
public void putBoolean(String key, boolean value) {
if (key == null) return;
switch (key) {
case "terminal_view_key_logging_enabled":
mPreferences.setTerminalViewKeyLoggingEnabled(value);
break;
case "plugin_error_notifications_enabled":
mPreferences.setPluginErrorNotificationsEnabled(value);
break;
case "crash_report_notifications_enabled":
mPreferences.setCrashReportNotificationsEnabled(value);
break;
default:
break;
}
}
@Override
public boolean getBoolean(String key, boolean defValue) {
switch (key) {
case "terminal_view_key_logging_enabled":
return mPreferences.getTerminalViewKeyLoggingEnabled();
case "plugin_error_notifications_enabled":
return mPreferences.getPluginErrorNotificationsEnabled();
case "crash_report_notifications_enabled":
return mPreferences.getCrashReportNotificationsEnabled();
default:
return false;
}
}
}

View File

@@ -0,0 +1,71 @@
package com.termux.app.fragments.settings;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.Keep;
import androidx.preference.PreferenceDataStore;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import com.termux.R;
import com.termux.shared.settings.preferences.TermuxAppSharedPreferences;
@Keep
public class TerminalIOPreferencesFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
PreferenceManager preferenceManager = getPreferenceManager();
preferenceManager.setPreferenceDataStore(TerminalIOPreferencesDataStore.getInstance(getContext()));
setPreferencesFromResource(R.xml.terminal_io_preferences, rootKey);
}
}
class TerminalIOPreferencesDataStore extends PreferenceDataStore {
private final Context mContext;
private final TermuxAppSharedPreferences mPreferences;
private static TerminalIOPreferencesDataStore mInstance;
private TerminalIOPreferencesDataStore(Context context) {
mContext = context;
mPreferences = new TermuxAppSharedPreferences(context);
}
public static synchronized TerminalIOPreferencesDataStore getInstance(Context context) {
if (mInstance == null) {
mInstance = new TerminalIOPreferencesDataStore(context.getApplicationContext());
}
return mInstance;
}
@Override
public void putBoolean(String key, boolean value) {
if (key == null) return;
switch (key) {
case "soft_keyboard_enabled":
mPreferences.setSoftKeyboardEnabled(value);
break;
default:
break;
}
}
@Override
public boolean getBoolean(String key, boolean defValue) {
switch (key) {
case "soft_keyboard_enabled":
return mPreferences.getSoftKeyboardEnabled();
default:
return false;
}
}
}

View File

@@ -0,0 +1,64 @@
package com.termux.app.models;
import com.termux.shared.markdown.MarkdownUtils;
import com.termux.shared.termux.TermuxUtils;
import java.io.Serializable;
public class ReportInfo implements Serializable {
/** The user action that was being processed for which the report was generated. */
public final UserAction userAction;
/** The internal app component that sent the report. */
public final String sender;
/** The report title. */
public final String reportTitle;
/** The markdown report text prefix. Will not be part of copy and share operations, etc. */
public final String reportStringPrefix;
/** The markdown report text. */
public final String reportString;
/** The markdown report text suffix. Will not be part of copy and share operations, etc. */
public final String reportStringSuffix;
/** If set to {@code true}, then report, app and device info will be added to the report when
* markdown is generated.
*/
public final boolean addReportInfoToMarkdown;
/** The timestamp for the report. */
public final String reportTimestamp;
public ReportInfo(UserAction userAction, String sender, String reportTitle, String reportStringPrefix, String reportString, String reportStringSuffix, boolean addReportInfoToMarkdown) {
this.userAction = userAction;
this.sender = sender;
this.reportTitle = reportTitle;
this.reportStringPrefix = reportStringPrefix;
this.reportString = reportString;
this.reportStringSuffix = reportStringSuffix;
this.addReportInfoToMarkdown = addReportInfoToMarkdown;
this.reportTimestamp = TermuxUtils.getCurrentTimeStamp();
}
/**
* Get a markdown {@link String} for {@link ReportInfo}.
*
* @param reportInfo The {@link ReportInfo} to convert.
* @return Returns the markdown {@link String}.
*/
public static String getReportInfoMarkdownString(final ReportInfo reportInfo) {
if (reportInfo == null) return "null";
StringBuilder markdownString = new StringBuilder();
if (reportInfo.addReportInfoToMarkdown) {
markdownString.append("## Report Info\n\n");
markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("User Action", reportInfo.userAction, "-"));
markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Sender", reportInfo.sender, "-"));
markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Report Timestamp", reportInfo.reportTimestamp, "-"));
markdownString.append("\n##\n\n");
}
markdownString.append(reportInfo.reportString);
return markdownString.toString();
}
}

View File

@@ -0,0 +1,19 @@
package com.termux.app.models;
public enum UserAction {
PLUGIN_EXECUTION_COMMAND("plugin execution command"),
CRASH_REPORT("crash report"),
REPORT_ISSUE_FROM_TRANSCRIPT("report issue from transcript");
private final String name;
UserAction(final String name) {
this.name = name;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,99 @@
package com.termux.app.settings.properties;
import android.content.Context;
import com.termux.app.terminal.io.KeyboardShortcut;
import com.termux.app.terminal.io.extrakeys.ExtraKeysInfo;
import com.termux.shared.logger.Logger;
import com.termux.shared.settings.properties.SharedPropertiesParser;
import com.termux.shared.settings.properties.TermuxPropertyConstants;
import com.termux.shared.settings.properties.TermuxSharedProperties;
import org.json.JSONException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
public class TermuxAppSharedProperties extends TermuxSharedProperties implements SharedPropertiesParser {
private ExtraKeysInfo mExtraKeysInfo;
private List<KeyboardShortcut> mSessionShortcuts = new ArrayList<>();
private static final String LOG_TAG = "TermuxAppSharedProperties";
public TermuxAppSharedProperties(@Nonnull Context context) {
super(context);
}
/**
* Reload the termux properties from disk into an in-memory cache.
*/
@Override
public void loadTermuxPropertiesFromDisk() {
super.loadTermuxPropertiesFromDisk();
setExtraKeys();
setSessionShortcuts();
}
/**
* Set the terminal extra keys and style.
*/
private void setExtraKeys() {
mExtraKeysInfo = null;
try {
// The mMap stores the extra key and style string values while loading properties
// Check {@link #getExtraKeysInternalPropertyValueFromValue(String)} and
// {@link #getExtraKeysStyleInternalPropertyValueFromValue(String)}
String extrakeys = (String) getInternalPropertyValue(TermuxPropertyConstants.KEY_EXTRA_KEYS, true);
String extraKeysStyle = (String) getInternalPropertyValue(TermuxPropertyConstants.KEY_EXTRA_KEYS_STYLE, true);
mExtraKeysInfo = new ExtraKeysInfo(extrakeys, extraKeysStyle);
} catch (JSONException e) {
Logger.showToast(mContext, "Could not load and set the \"" + TermuxPropertyConstants.KEY_EXTRA_KEYS + "\" property from the properties file: " + e.toString(), true);
Logger.logStackTraceWithMessage(LOG_TAG, "Could not load and set the \"" + TermuxPropertyConstants.KEY_EXTRA_KEYS + "\" property from the properties file: ", e);
try {
mExtraKeysInfo = new ExtraKeysInfo(TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS, TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS_STYLE);
} catch (JSONException e2) {
Logger.showToast(mContext, "Can't create default extra keys",true);
Logger.logStackTraceWithMessage(LOG_TAG, "Could create default extra keys: ", e);
mExtraKeysInfo = null;
}
}
}
/**
* Set the terminal sessions shortcuts.
*/
private void setSessionShortcuts() {
if (mSessionShortcuts == null)
mSessionShortcuts = new ArrayList<>();
else
mSessionShortcuts.clear();
// The {@link TermuxPropertyConstants#MAP_SESSION_SHORTCUTS} stores the session shortcut key and action pair
for (Map.Entry<String, Integer> entry : TermuxPropertyConstants.MAP_SESSION_SHORTCUTS.entrySet()) {
// The mMap stores the code points for the session shortcuts while loading properties
Integer codePoint = (Integer) getInternalPropertyValue(entry.getKey(), true);
// If codePoint is null, then session shortcut did not exist in properties or was invalid
// as parsed by {@link #getCodePointForSessionShortcuts(String,String)}
// If codePoint is not null, then get the action for the MAP_SESSION_SHORTCUTS key and
// add the code point to sessionShortcuts
if (codePoint != null)
mSessionShortcuts.add(new KeyboardShortcut(codePoint, entry.getValue()));
}
}
public List<KeyboardShortcut> getSessionShortcuts() {
return mSessionShortcuts;
}
public ExtraKeysInfo getExtraKeysInfo() {
return mExtraKeysInfo;
}
}

View File

@@ -0,0 +1,107 @@
package com.termux.app.terminal;
import android.annotation.SuppressLint;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.termux.R;
import com.termux.app.TermuxActivity;
import com.termux.shared.shell.TermuxSession;
import com.termux.terminal.TerminalSession;
import java.util.List;
public class TermuxSessionsListViewController extends ArrayAdapter<TermuxSession> implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener {
final TermuxActivity mActivity;
final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);
public TermuxSessionsListViewController(TermuxActivity activity, List<TermuxSession> sessionList) {
super(activity.getApplicationContext(), R.layout.item_terminal_sessions_list, sessionList);
this.mActivity = activity;
}
@SuppressLint("SetTextI18n")
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
View sessionRowView = convertView;
if (sessionRowView == null) {
LayoutInflater inflater = mActivity.getLayoutInflater();
sessionRowView = inflater.inflate(R.layout.item_terminal_sessions_list, parent, false);
}
TextView sessionTitleView = sessionRowView.findViewById(R.id.session_title);
TerminalSession sessionAtRow = getItem(position).getTerminalSession();
if (sessionAtRow == null) {
sessionTitleView.setText("null session");
return sessionRowView;
}
boolean isUsingBlackUI = mActivity.getProperties().isUsingBlackUI();
if (isUsingBlackUI) {
sessionTitleView.setBackground(
ContextCompat.getDrawable(mActivity, R.drawable.session_background_black_selected)
);
}
String name = sessionAtRow.mSessionName;
String sessionTitle = sessionAtRow.getTitle();
String numberPart = "[" + (position + 1) + "] ";
String sessionNamePart = (TextUtils.isEmpty(name) ? "" : name);
String sessionTitlePart = (TextUtils.isEmpty(sessionTitle) ? "" : ((sessionNamePart.isEmpty() ? "" : "\n") + sessionTitle));
String fullSessionTitle = numberPart + sessionNamePart + sessionTitlePart;
SpannableString fullSessionTitleStyled = new SpannableString(fullSessionTitle);
fullSessionTitleStyled.setSpan(boldSpan, 0, numberPart.length() + sessionNamePart.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
fullSessionTitleStyled.setSpan(italicSpan, numberPart.length() + sessionNamePart.length(), fullSessionTitle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sessionTitleView.setText(fullSessionTitleStyled);
boolean sessionRunning = sessionAtRow.isRunning();
if (sessionRunning) {
sessionTitleView.setPaintFlags(sessionTitleView.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
} else {
sessionTitleView.setPaintFlags(sessionTitleView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}
int defaultColor = isUsingBlackUI ? Color.WHITE : Color.BLACK;
int color = sessionRunning || sessionAtRow.getExitStatus() == 0 ? defaultColor : Color.RED;
sessionTitleView.setTextColor(color);
return sessionRowView;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TermuxSession clickedSession = getItem(position);
mActivity.getTermuxTerminalSessionClient().setCurrentSession(clickedSession.getTerminalSession());
mActivity.getDrawer().closeDrawers();
}
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
final TermuxSession selectedSession = getItem(position);
mActivity.getTermuxTerminalSessionClient().renameSession(selectedSession.getTerminalSession());
return true;
}
}

View File

@@ -0,0 +1,343 @@
package com.termux.app.terminal;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.media.AudioAttributes;
import android.media.SoundPool;
import android.text.TextUtils;
import android.widget.ListView;
import com.termux.R;
import com.termux.shared.shell.TermuxSession;
import com.termux.shared.interact.DialogUtils;
import com.termux.app.TermuxActivity;
import com.termux.shared.terminal.TermuxTerminalSessionClientBase;
import com.termux.shared.termux.TermuxConstants;
import com.termux.app.TermuxService;
import com.termux.shared.settings.properties.TermuxPropertyConstants;
import com.termux.app.terminal.io.BellHandler;
import com.termux.shared.logger.Logger;
import com.termux.terminal.TerminalColors;
import com.termux.terminal.TerminalSession;
import com.termux.terminal.TextStyle;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;
public class TermuxTerminalSessionClient extends TermuxTerminalSessionClientBase {
private final TermuxActivity mActivity;
private static final int MAX_SESSIONS = 8;
private final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
private final int mBellSoundId;
private static final String LOG_TAG = "TermuxTerminalSessionClient";
public TermuxTerminalSessionClient(TermuxActivity activity) {
this.mActivity = activity;
mBellSoundId = mBellSoundPool.load(activity, R.raw.bell, 1);
}
@Override
public void onTextChanged(TerminalSession changedSession) {
if (!mActivity.isVisible()) return;
if (mActivity.getCurrentSession() == changedSession) mActivity.getTerminalView().onScreenUpdated();
}
@Override
public void onTitleChanged(TerminalSession updatedSession) {
if (!mActivity.isVisible()) return;
if (updatedSession != mActivity.getCurrentSession()) {
// Only show toast for other sessions than the current one, since the user
// probably consciously caused the title change to change in the current session
// and don't want an annoying toast for that.
mActivity.showToast(toToastTitle(updatedSession), true);
}
termuxSessionListNotifyUpdated();
}
@Override
public void onSessionFinished(final TerminalSession finishedSession) {
if (mActivity.getTermuxService().wantsToStop()) {
// The service wants to stop as soon as possible.
mActivity.finishActivityIfNotFinishing();
return;
}
if (mActivity.isVisible() && finishedSession != mActivity.getCurrentSession()) {
// Show toast for non-current sessions that exit.
int indexOfSession = mActivity.getTermuxService().getIndexOfSession(finishedSession);
// Verify that session was not removed before we got told about it finishing:
if (indexOfSession >= 0)
mActivity.showToast(toToastTitle(finishedSession) + " - exited", true);
}
if (mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
// On Android TV devices we need to use older behaviour because we may
// not be able to have multiple launcher icons.
if (mActivity.getTermuxService().getTermuxSessionsSize() > 1) {
removeFinishedSession(finishedSession);
}
} else {
// Once we have a separate launcher icon for the failsafe session, it
// should be safe to auto-close session on exit code '0' or '130'.
if (finishedSession.getExitStatus() == 0 || finishedSession.getExitStatus() == 130) {
removeFinishedSession(finishedSession);
}
}
}
@Override
public void onClipboardText(TerminalSession session, String text) {
if (!mActivity.isVisible()) return;
ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text)));
}
@Override
public void onBell(TerminalSession session) {
if (!mActivity.isVisible()) return;
switch (mActivity.getProperties().getBellBehaviour()) {
case TermuxPropertyConstants.IVALUE_BELL_BEHAVIOUR_VIBRATE:
BellHandler.getInstance(mActivity).doBell();
break;
case TermuxPropertyConstants.IVALUE_BELL_BEHAVIOUR_BEEP:
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
break;
case TermuxPropertyConstants.IVALUE_BELL_BEHAVIOUR_IGNORE:
// Ignore the bell character.
break;
}
}
@Override
public void onColorsChanged(TerminalSession changedSession) {
if (mActivity.getCurrentSession() == changedSession)
updateBackgroundColor();
}
/** Try switching to session. */
public void setCurrentSession(TerminalSession session) {
if (session == null) return;
if (mActivity.getTerminalView().attachSession(session)) {
// notify about switched session if not already displaying the session
notifyOfSessionChange();
}
// We call the following even when the session is already being displayed since config may
// be stale, like current session not selected or scrolled to.
checkAndScrollToSession(session);
updateBackgroundColor();
}
void notifyOfSessionChange() {
if (!mActivity.isVisible()) return;
TerminalSession session = mActivity.getCurrentSession();
mActivity.showToast(toToastTitle(session), false);
}
public void switchToSession(boolean forward) {
TermuxService service = mActivity.getTermuxService();
TerminalSession currentTerminalSession = mActivity.getCurrentSession();
int index = service.getIndexOfSession(currentTerminalSession);
int size = service.getTermuxSessionsSize();
if (forward) {
if (++index >= size) index = 0;
} else {
if (--index < 0) index = size - 1;
}
TermuxSession termuxSession = service.getTermuxSession(index);
if (termuxSession != null)
setCurrentSession(termuxSession.getTerminalSession());
}
public void switchToSession(int index) {
TermuxSession termuxSession = mActivity.getTermuxService().getTermuxSession(index);
if (termuxSession != null)
setCurrentSession(termuxSession.getTerminalSession());
}
@SuppressLint("InflateParams")
public void renameSession(final TerminalSession sessionToRename) {
if (sessionToRename == null) return;
DialogUtils.textInput(mActivity, R.string.title_rename_session, sessionToRename.mSessionName, R.string.action_rename_session_confirm, text -> {
sessionToRename.mSessionName = text;
termuxSessionListNotifyUpdated();
}, -1, null, -1, null, null);
}
public void addNewSession(boolean isFailSafe, String sessionName) {
if (mActivity.getTermuxService().getTermuxSessionsSize() >= MAX_SESSIONS) {
new AlertDialog.Builder(mActivity).setTitle(R.string.title_max_terminals_reached).setMessage(R.string.msg_max_terminals_reached)
.setPositiveButton(android.R.string.ok, null).show();
} else {
TerminalSession currentSession = mActivity.getCurrentSession();
String workingDirectory;
if (currentSession == null) {
workingDirectory = mActivity.getProperties().getDefaultWorkingDirectory();
} else {
workingDirectory = currentSession.getCwd();
}
TermuxSession newTermuxSession = mActivity.getTermuxService().createTermuxSession(null, null, null, workingDirectory, isFailSafe, sessionName);
if (newTermuxSession == null) return;
TerminalSession newTerminalSession = newTermuxSession.getTerminalSession();
setCurrentSession(newTerminalSession);
mActivity.getDrawer().closeDrawers();
}
}
public void setCurrentStoredSession() {
TerminalSession currentSession = mActivity.getCurrentSession();
if (currentSession != null)
mActivity.getPreferences().setCurrentSession(currentSession.mHandle);
else
mActivity.getPreferences().setCurrentSession(null);
}
/** The current session as stored or the last one if that does not exist. */
public TerminalSession getCurrentStoredSessionOrLast() {
TerminalSession stored = getCurrentStoredSession(mActivity);
if (stored != null) {
// If a stored session is in the list of currently running sessions, then return it
return stored;
} else {
// Else return the last session currently running
TermuxSession termuxSession = mActivity.getTermuxService().getLastTermuxSession();
if (termuxSession != null)
return termuxSession.getTerminalSession();
else
return null;
}
}
private TerminalSession getCurrentStoredSession(TermuxActivity context) {
String sessionHandle = mActivity.getPreferences().getCurrentSession();
// If no session is stored in shared preferences
if (sessionHandle == null)
return null;
// Check if the session handle found matches one of the currently running sessions
return context.getTermuxService().getTerminalSessionForHandle(sessionHandle);
}
public void removeFinishedSession(TerminalSession finishedSession) {
// Return pressed with finished session - remove it.
TermuxService service = mActivity.getTermuxService();
int index = service.removeTermuxSession(finishedSession);
int size = mActivity.getTermuxService().getTermuxSessionsSize();
if (size == 0) {
// There are no sessions to show, so finish the activity.
mActivity.finishActivityIfNotFinishing();
} else {
if (index >= size) {
index = size - 1;
}
TermuxSession termuxSession = service.getTermuxSession(index);
if (termuxSession != null)
setCurrentSession(termuxSession.getTerminalSession());
}
}
public void termuxSessionListNotifyUpdated() {
mActivity.termuxSessionListNotifyUpdated();
}
public void checkAndScrollToSession(TerminalSession session) {
if (!mActivity.isVisible()) return;
final int indexOfSession = mActivity.getTermuxService().getIndexOfSession(session);
if (indexOfSession < 0) return;
final ListView termuxSessionsListView = mActivity.findViewById(R.id.terminal_sessions_list);
if (termuxSessionsListView == null) return;
termuxSessionsListView.setItemChecked(indexOfSession, true);
// Delay is necessary otherwise sometimes scroll to newly added session does not happen
termuxSessionsListView.postDelayed(() -> termuxSessionsListView.smoothScrollToPosition(indexOfSession), 1000);
}
String toToastTitle(TerminalSession session) {
final int indexOfSession = mActivity.getTermuxService().getIndexOfSession(session);
if (indexOfSession < 0) return null;
StringBuilder toastTitle = new StringBuilder("[" + (indexOfSession + 1) + "]");
if (!TextUtils.isEmpty(session.mSessionName)) {
toastTitle.append(" ").append(session.mSessionName);
}
String title = session.getTitle();
if (!TextUtils.isEmpty(title)) {
// Space to "[${NR}] or newline after session name:
toastTitle.append(session.mSessionName == null ? " " : "\n");
toastTitle.append(title);
}
return toastTitle.toString();
}
public void checkForFontAndColors() {
try {
File colorsFile = TermuxConstants.TERMUX_COLOR_PROPERTIES_FILE;
File fontFile = TermuxConstants.TERMUX_FONT_FILE;
final Properties props = new Properties();
if (colorsFile.isFile()) {
try (InputStream in = new FileInputStream(colorsFile)) {
props.load(in);
}
}
TerminalColors.COLOR_SCHEME.updateWith(props);
TerminalSession session = mActivity.getCurrentSession();
if (session != null && session.getEmulator() != null) {
session.getEmulator().mColors.reset();
}
updateBackgroundColor();
final Typeface newTypeface = (fontFile.exists() && fontFile.length() > 0) ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
mActivity.getTerminalView().setTypeface(newTypeface);
} catch (Exception e) {
Logger.logStackTraceWithMessage(LOG_TAG, "Error in checkForFontAndColors()", e);
}
}
public void updateBackgroundColor() {
if (!mActivity.isVisible()) return;
TerminalSession session = mActivity.getCurrentSession();
if (session != null && session.getEmulator() != null) {
mActivity.getWindow().getDecorView().setBackgroundColor(session.getEmulator().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]);
}
}
}

View File

@@ -0,0 +1,443 @@
package com.termux.app.terminal;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.net.Uri;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.InputMethodManager;
import android.widget.ListView;
import android.widget.Toast;
import com.termux.R;
import com.termux.app.TermuxActivity;
import com.termux.shared.shell.ShellUtils;
import com.termux.shared.terminal.TermuxTerminalViewClientBase;
import com.termux.shared.termux.TermuxConstants;
import com.termux.app.activities.ReportActivity;
import com.termux.app.models.ReportInfo;
import com.termux.app.models.UserAction;
import com.termux.app.terminal.io.KeyboardShortcut;
import com.termux.app.terminal.io.extrakeys.ExtraKeysView;
import com.termux.shared.settings.properties.TermuxPropertyConstants;
import com.termux.shared.data.DataUtils;
import com.termux.shared.logger.Logger;
import com.termux.shared.markdown.MarkdownUtils;
import com.termux.shared.termux.TermuxUtils;
import com.termux.terminal.KeyHandler;
import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import androidx.drawerlayout.widget.DrawerLayout;
public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
final TermuxActivity mActivity;
final TermuxTerminalSessionClient mTermuxTerminalSessionClient;
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
public TermuxTerminalViewClient(TermuxActivity activity, TermuxTerminalSessionClient termuxTerminalSessionClient) {
this.mActivity = activity;
this.mTermuxTerminalSessionClient = termuxTerminalSessionClient;
}
@Override
public float onScale(float scale) {
if (scale < 0.9f || scale > 1.1f) {
boolean increase = scale > 1.f;
changeFontSize(increase);
return 1.0f;
}
return scale;
}
@Override
public void onSingleTapUp(MotionEvent e) {
InputMethodManager mgr = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
mgr.showSoftInput(mActivity.getTerminalView(), InputMethodManager.SHOW_IMPLICIT);
}
@Override
public boolean shouldBackButtonBeMappedToEscape() {
return mActivity.getProperties().isBackKeyTheEscapeKey();
}
@Override
public boolean shouldEnforceCharBasedInput() {
return mActivity.getProperties().isEnforcingCharBasedInput();
}
@Override
public boolean shouldUseCtrlSpaceWorkaround() {
return mActivity.getProperties().isUsingCtrlSpaceWorkaround();
}
@Override
public void copyModeChanged(boolean copyMode) {
// Disable drawer while copying.
mActivity.getDrawer().setDrawerLockMode(copyMode ? DrawerLayout.LOCK_MODE_LOCKED_CLOSED : DrawerLayout.LOCK_MODE_UNLOCKED);
}
@SuppressLint("RtlHardcoded")
@Override
public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession currentSession) {
if (handleVirtualKeys(keyCode, e, true)) return true;
if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) {
mTermuxTerminalSessionClient.removeFinishedSession(currentSession);
return true;
} else if (e.isCtrlPressed() && e.isAltPressed()) {
// Get the unmodified code point:
int unicodeChar = e.getUnicodeChar(0);
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || unicodeChar == 'n'/* next */) {
mTermuxTerminalSessionClient.switchToSession(true);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP || unicodeChar == 'p' /* previous */) {
mTermuxTerminalSessionClient.switchToSession(false);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
mActivity.getDrawer().openDrawer(Gravity.LEFT);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
mActivity.getDrawer().closeDrawers();
} else if (unicodeChar == 'k'/* keyboard */) {
InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
} else if (unicodeChar == 'm'/* menu */) {
mActivity.getTerminalView().showContextMenu();
} else if (unicodeChar == 'r'/* rename */) {
mTermuxTerminalSessionClient.renameSession(currentSession);
} else if (unicodeChar == 'c'/* create */) {
mTermuxTerminalSessionClient.addNewSession(false, null);
} else if (unicodeChar == 'u' /* urls */) {
showUrlSelection();
} else if (unicodeChar == 'v') {
doPaste();
} else if (unicodeChar == '+' || e.getUnicodeChar(KeyEvent.META_SHIFT_ON) == '+') {
// We also check for the shifted char here since shift may be required to produce '+',
// see https://github.com/termux/termux-api/issues/2
changeFontSize(true);
} else if (unicodeChar == '-') {
changeFontSize(false);
} else if (unicodeChar >= '1' && unicodeChar <= '9') {
int index = unicodeChar - '1';
mTermuxTerminalSessionClient.switchToSession(index);
}
return true;
}
return false;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent e) {
return handleVirtualKeys(keyCode, e, false);
}
/** Handle dedicated volume buttons as virtual keys if applicable. */
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
InputDevice inputDevice = event.getDevice();
if (mActivity.getProperties().areVirtualVolumeKeysDisabled()) {
return false;
} else if (inputDevice != null && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
// Do not steal dedicated buttons from a full external keyboard.
return false;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
mVirtualControlKeyDown = down;
return true;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
mVirtualFnKeyDown = down;
return true;
}
return false;
}
@Override
public boolean readControlKey() {
return (mActivity.getExtraKeysView() != null && mActivity.getExtraKeysView().readSpecialButton(ExtraKeysView.SpecialButton.CTRL)) || mVirtualControlKeyDown;
}
@Override
public boolean readAltKey() {
return (mActivity.getExtraKeysView() != null && mActivity.getExtraKeysView().readSpecialButton(ExtraKeysView.SpecialButton.ALT));
}
@Override
public boolean onLongPress(MotionEvent event) {
return false;
}
@Override
public boolean onCodePoint(final int codePoint, boolean ctrlDown, TerminalSession session) {
if (mVirtualFnKeyDown) {
int resultingKeyCode = -1;
int resultingCodePoint = -1;
boolean altDown = false;
int lowerCase = Character.toLowerCase(codePoint);
switch (lowerCase) {
// Arrow keys.
case 'w':
resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP;
break;
case 'a':
resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT;
break;
case 's':
resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN;
break;
case 'd':
resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
break;
// Page up and down.
case 'p':
resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP;
break;
case 'n':
resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN;
break;
// Some special keys:
case 't':
resultingKeyCode = KeyEvent.KEYCODE_TAB;
break;
case 'i':
resultingKeyCode = KeyEvent.KEYCODE_INSERT;
break;
case 'h':
resultingCodePoint = '~';
break;
// Special characters to input.
case 'u':
resultingCodePoint = '_';
break;
case 'l':
resultingCodePoint = '|';
break;
// Function keys.
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
resultingKeyCode = (codePoint - '1') + KeyEvent.KEYCODE_F1;
break;
case '0':
resultingKeyCode = KeyEvent.KEYCODE_F10;
break;
// Other special keys.
case 'e':
resultingCodePoint = /*Escape*/ 27;
break;
case '.':
resultingCodePoint = /*^.*/ 28;
break;
case 'b': // alt+b, jumping backward in readline.
case 'f': // alf+f, jumping forward in readline.
case 'x': // alt+x, common in emacs.
resultingCodePoint = lowerCase;
altDown = true;
break;
// Volume control.
case 'v':
resultingCodePoint = -1;
AudioManager audio = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI);
break;
// Writing mode:
case 'q':
case 'k':
mActivity.toggleTerminalToolbar();
mVirtualFnKeyDown=false; // force disable fn key down to restore keyboard input into terminal view, fixes termux/termux-app#1420
break;
}
if (resultingKeyCode != -1) {
TerminalEmulator term = session.getEmulator();
session.write(KeyHandler.getCode(resultingKeyCode, 0, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode()));
} else if (resultingCodePoint != -1) {
session.writeCodePoint(altDown, resultingCodePoint);
}
return true;
} else if (ctrlDown) {
if (codePoint == 106 /* Ctrl+j or \n */ && !session.isRunning()) {
mTermuxTerminalSessionClient.removeFinishedSession(session);
return true;
}
List<KeyboardShortcut> shortcuts = mActivity.getProperties().getSessionShortcuts();
if (shortcuts != null && !shortcuts.isEmpty()) {
int codePointLowerCase = Character.toLowerCase(codePoint);
for (int i = shortcuts.size() - 1; i >= 0; i--) {
KeyboardShortcut shortcut = shortcuts.get(i);
if (codePointLowerCase == shortcut.codePoint) {
switch (shortcut.shortcutAction) {
case TermuxPropertyConstants.ACTION_SHORTCUT_CREATE_SESSION:
mTermuxTerminalSessionClient.addNewSession(false, null);
return true;
case TermuxPropertyConstants.ACTION_SHORTCUT_NEXT_SESSION:
mTermuxTerminalSessionClient.switchToSession(true);
return true;
case TermuxPropertyConstants.ACTION_SHORTCUT_PREVIOUS_SESSION:
mTermuxTerminalSessionClient.switchToSession(false);
return true;
case TermuxPropertyConstants.ACTION_SHORTCUT_RENAME_SESSION:
mTermuxTerminalSessionClient.renameSession(mActivity.getCurrentSession());
return true;
}
}
}
}
}
return false;
}
public void changeFontSize(boolean increase) {
mActivity.getPreferences().changeFontSize(increase);
mActivity.getTerminalView().setTextSize(mActivity.getPreferences().getFontSize());
}
public void shareSessionTranscript() {
TerminalSession session = mActivity.getCurrentSession();
if (session == null) return;
String transcriptText = ShellUtils.getTerminalSessionTranscriptText(session, false, true);
if (transcriptText == null) return;
try {
// See https://github.com/termux/termux-app/issues/1166.
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
transcriptText = DataUtils.getTruncatedCommandOutput(transcriptText, DataUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES, false, true, false).trim();
intent.putExtra(Intent.EXTRA_TEXT, transcriptText);
intent.putExtra(Intent.EXTRA_SUBJECT, mActivity.getString(R.string.title_share_transcript));
mActivity.startActivity(Intent.createChooser(intent, mActivity.getString(R.string.title_share_transcript_with)));
} catch (Exception e) {
Logger.logStackTraceWithMessage("Failed to get share session transcript of length " + transcriptText.length(), e);
}
}
public void showUrlSelection() {
TerminalSession session = mActivity.getCurrentSession();
if (session == null) return;
String text = ShellUtils.getTerminalSessionTranscriptText(session, true, true);
LinkedHashSet<CharSequence> urlSet = DataUtils.extractUrls(text);
if (urlSet.isEmpty()) {
new AlertDialog.Builder(mActivity).setMessage(R.string.title_select_url_none_found).show();
return;
}
final CharSequence[] urls = urlSet.toArray(new CharSequence[0]);
Collections.reverse(Arrays.asList(urls)); // Latest first.
// Click to copy url to clipboard:
final AlertDialog dialog = new AlertDialog.Builder(mActivity).setItems(urls, (di, which) -> {
String url = (String) urls[which];
ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(url)));
Toast.makeText(mActivity, R.string.msg_select_url_copied_to_clipboard, Toast.LENGTH_LONG).show();
}).setTitle(R.string.title_select_url_dialog).create();
// Long press to open URL:
dialog.setOnShowListener(di -> {
ListView lv = dialog.getListView(); // this is a ListView with your "buds" in it
lv.setOnItemLongClickListener((parent, view, position, id) -> {
dialog.dismiss();
String url = (String) urls[position];
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
try {
mActivity.startActivity(i, null);
} catch (ActivityNotFoundException e) {
// If no applications match, Android displays a system message.
mActivity.startActivity(Intent.createChooser(i, null));
}
return true;
});
});
dialog.show();
}
public void reportIssueFromTranscript() {
TerminalSession session = mActivity.getCurrentSession();
if (session == null) return;
String transcriptText = ShellUtils.getTerminalSessionTranscriptText(session, false, true);
if (transcriptText == null) return;
transcriptText = DataUtils.getTruncatedCommandOutput(transcriptText, DataUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES, false, true, false).trim();
StringBuilder reportString = new StringBuilder();
String title = TermuxConstants.TERMUX_APP_NAME + " Report Issue";
reportString.append("## Transcript\n");
reportString.append("\n").append(MarkdownUtils.getMarkdownCodeForString(transcriptText, true));
reportString.append("\n\n").append(TermuxUtils.getAppInfoMarkdownString(mActivity, true));
reportString.append("\n\n").append(TermuxUtils.getDeviceInfoMarkdownString(mActivity));
String termuxAptInfo = TermuxUtils.geAPTInfoMarkdownString(mActivity);
if (termuxAptInfo != null)
reportString.append("\n\n").append(termuxAptInfo);
ReportActivity.startReportActivity(mActivity, new ReportInfo(UserAction.REPORT_ISSUE_FROM_TRANSCRIPT, TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY_NAME, title, null, reportString.toString(), "\n\n" + TermuxUtils.getReportIssueMarkdownString(mActivity), false));
}
public void doPaste() {
TerminalSession session = mActivity.getCurrentSession();
if (session == null) return;
if (!session.isRunning()) return;
ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = clipboard.getPrimaryClip();
if (clipData == null) return;
CharSequence paste = clipData.getItemAt(0).coerceToText(mActivity);
if (!TextUtils.isEmpty(paste))
session.getEmulator().paste(paste.toString());
}
}

View File

@@ -0,0 +1,63 @@
package com.termux.app.terminal.io;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.os.Vibrator;
public class BellHandler {
private static BellHandler instance = null;
private static final Object lock = new Object();
public static BellHandler getInstance(Context context) {
if (instance == null) {
synchronized (lock) {
if (instance == null) {
instance = new BellHandler((Vibrator) context.getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE));
}
}
}
return instance;
}
private static final long DURATION = 50;
private static final long MIN_PAUSE = 3 * DURATION;
private final Handler handler = new Handler(Looper.getMainLooper());
private long lastBell = 0;
private final Runnable bellRunnable;
private BellHandler(final Vibrator vibrator) {
bellRunnable = new Runnable() {
@Override
public void run() {
if (vibrator != null) {
vibrator.vibrate(DURATION);
}
}
};
}
public synchronized void doBell() {
long now = now();
long timeSinceLastBell = now - lastBell;
if (timeSinceLastBell < 0) {
// there is a next bell pending; don't schedule another one
} else if (timeSinceLastBell < MIN_PAUSE) {
// there was a bell recently, schedule the next one
handler.postDelayed(bellRunnable, MIN_PAUSE - timeSinceLastBell);
lastBell = lastBell + MIN_PAUSE;
} else {
// the last bell was long ago, do it now
bellRunnable.run();
lastBell = now;
}
}
private long now() {
return SystemClock.uptimeMillis();
}
}

View File

@@ -0,0 +1,68 @@
package com.termux.app.terminal.io;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import com.termux.app.TermuxActivity;
/**
* Work around for fullscreen mode in Termux to fix ExtraKeysView not being visible.
* This class is derived from:
* https://stackoverflow.com/questions/7417123/android-how-to-adjust-layout-in-full-screen-mode-when-softkeyboard-is-visible
* and has some additional tweaks
* ---
* For more information, see https://issuetracker.google.com/issues/36911528
*/
public class FullScreenWorkAround {
private final View mChildOfContent;
private int mUsableHeightPrevious;
private final ViewGroup.LayoutParams mViewGroupLayoutParams;
private final int mNavBarHeight;
public static void apply(TermuxActivity activity) {
new FullScreenWorkAround(activity);
}
private FullScreenWorkAround(TermuxActivity activity) {
ViewGroup content = activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
mViewGroupLayoutParams = mChildOfContent.getLayoutParams();
mNavBarHeight = activity.getNavBarHeight();
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent);
}
private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != mUsableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard / 4)) {
// keyboard probably just became visible
// ensures that usable layout space does not extend behind the
// soft keyboard, causing the extra keys to not be visible
mViewGroupLayoutParams.height = (usableHeightSansKeyboard - heightDifference) + getNavBarHeight();
} else {
// keyboard probably just became hidden
mViewGroupLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
mUsableHeightPrevious = usableHeightNow;
}
}
private int getNavBarHeight() {
return mNavBarHeight;
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);
}
}

View File

@@ -0,0 +1,13 @@
package com.termux.app.terminal.io;
public class KeyboardShortcut {
public final int codePoint;
public final int shortcutAction;
public KeyboardShortcut(int codePoint, int shortcutAction) {
this.codePoint = codePoint;
this.shortcutAction = shortcutAction;
}
}

View File

@@ -0,0 +1,114 @@
package com.termux.app.terminal.io;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.termux.R;
import com.termux.app.TermuxActivity;
import com.termux.app.terminal.io.extrakeys.ExtraKeysView;
import com.termux.terminal.TerminalSession;
public class TerminalToolbarViewPager {
public static class PageAdapter extends PagerAdapter {
final TermuxActivity mActivity;
String mSavedTextInput;
public PageAdapter(TermuxActivity activity, String savedTextInput) {
this.mActivity = activity;
this.mSavedTextInput = savedTextInput;
}
@Override
public int getCount() {
return 2;
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup collection, int position) {
LayoutInflater inflater = LayoutInflater.from(mActivity);
View layout;
if (position == 0) {
layout = inflater.inflate(R.layout.view_terminal_toolbar_extra_keys, collection, false);
ExtraKeysView extraKeysView = (ExtraKeysView) layout;
mActivity.setExtraKeysView(extraKeysView);
extraKeysView.reload(mActivity.getProperties().getExtraKeysInfo());
// apply extra keys fix if enabled in prefs
if (mActivity.getProperties().isUsingFullScreen() && mActivity.getProperties().isUsingFullScreenWorkAround()) {
FullScreenWorkAround.apply(mActivity);
}
} else {
layout = inflater.inflate(R.layout.view_terminal_toolbar_text_input, collection, false);
final EditText editText = layout.findViewById(R.id.terminal_toolbar_text_input);
if (mSavedTextInput != null) {
editText.setText(mSavedTextInput);
mSavedTextInput = null;
}
editText.setOnEditorActionListener((v, actionId, event) -> {
TerminalSession session = mActivity.getCurrentSession();
if (session != null) {
if (session.isRunning()) {
String textToSend = editText.getText().toString();
if (textToSend.length() == 0) textToSend = "\r";
session.write(textToSend);
} else {
mActivity.getTermuxTerminalSessionClient().removeFinishedSession(session);
}
editText.setText("");
}
return true;
});
}
collection.addView(layout);
return layout;
}
@Override
public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) {
collection.removeView((View) view);
}
}
public static class OnPageChangeListener extends ViewPager.SimpleOnPageChangeListener {
final TermuxActivity mActivity;
final ViewPager mTerminalToolbarViewPager;
public OnPageChangeListener(TermuxActivity activity, ViewPager viewPager) {
this.mActivity = activity;
this.mTerminalToolbarViewPager = viewPager;
}
@Override
public void onPageSelected(int position) {
if (position == 0) {
mActivity.getTerminalView().requestFocus();
} else {
final EditText editText = mTerminalToolbarViewPager.findViewById(R.id.terminal_toolbar_text_input);
if (editText != null) editText.requestFocus();
}
}
}
}

View File

@@ -0,0 +1,92 @@
package com.termux.app.terminal.io.extrakeys;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Arrays;
import java.util.stream.Collectors;
public class ExtraKeyButton {
/**
* The key that will be sent to the terminal, either a control character
* defined in ExtraKeysView.keyCodesForString (LEFT, RIGHT, PGUP...) or
* some text.
*/
private final String key;
/**
* If the key is a macro, i.e. a sequence of keys separated by space.
*/
private final boolean macro;
/**
* The text that will be shown on the button.
*/
private final String display;
/**
* The information of the popup (triggered by swipe up).
*/
@Nullable
private ExtraKeyButton popup;
public ExtraKeyButton(ExtraKeysInfo.CharDisplayMap charDisplayMap, JSONObject config) throws JSONException {
this(charDisplayMap, config, null);
}
public ExtraKeyButton(ExtraKeysInfo.CharDisplayMap charDisplayMap, JSONObject config, @Nullable ExtraKeyButton popup) throws JSONException {
String keyFromConfig = config.optString("key", null);
String macroFromConfig = config.optString("macro", null);
String[] keys;
if (keyFromConfig != null && macroFromConfig != null) {
throw new JSONException("Both key and macro can't be set for the same key");
} else if (keyFromConfig != null) {
keys = new String[]{keyFromConfig};
this.macro = false;
} else if (macroFromConfig != null) {
keys = macroFromConfig.split(" ");
this.macro = true;
} else {
throw new JSONException("All keys have to specify either key or macro");
}
for (int i = 0; i < keys.length; i++) {
keys[i] = ExtraKeysInfo.replaceAlias(keys[i]);
}
this.key = TextUtils.join(" ", keys);
String displayFromConfig = config.optString("display", null);
if (displayFromConfig != null) {
this.display = displayFromConfig;
} else {
this.display = Arrays.stream(keys)
.map(key -> charDisplayMap.get(key, key))
.collect(Collectors.joining(" "));
}
this.popup = popup;
}
public String getKey() {
return key;
}
public boolean isMacro() {
return macro;
}
public String getDisplay() {
return display;
}
@Nullable
public ExtraKeyButton getPopup() {
return popup;
}
}

View File

@@ -0,0 +1,253 @@
package com.termux.app.terminal.io.extrakeys;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
public class ExtraKeysInfo {
/**
* Matrix of buttons displayed
*/
private final ExtraKeyButton[][] buttons;
/**
* This corresponds to one of the CharMapDisplay below
*/
private String style;
public ExtraKeysInfo(String propertiesInfo, String style) throws JSONException {
this.style = style;
// Convert String propertiesInfo to Array of Arrays
JSONArray arr = new JSONArray(propertiesInfo);
Object[][] matrix = new Object[arr.length()][];
for (int i = 0; i < arr.length(); i++) {
JSONArray line = arr.getJSONArray(i);
matrix[i] = new Object[line.length()];
for (int j = 0; j < line.length(); j++) {
matrix[i][j] = line.get(j);
}
}
// convert matrix to buttons
this.buttons = new ExtraKeyButton[matrix.length][];
for (int i = 0; i < matrix.length; i++) {
this.buttons[i] = new ExtraKeyButton[matrix[i].length];
for (int j = 0; j < matrix[i].length; j++) {
Object key = matrix[i][j];
JSONObject jobject = normalizeKeyConfig(key);
ExtraKeyButton button;
if (! jobject.has("popup")) {
// no popup
button = new ExtraKeyButton(getSelectedCharMap(), jobject);
} else {
// a popup
JSONObject popupJobject = normalizeKeyConfig(jobject.get("popup"));
ExtraKeyButton popup = new ExtraKeyButton(getSelectedCharMap(), popupJobject);
button = new ExtraKeyButton(getSelectedCharMap(), jobject, popup);
}
this.buttons[i][j] = button;
}
}
}
/**
* "hello" -> {"key": "hello"}
*/
private static JSONObject normalizeKeyConfig(Object key) throws JSONException {
JSONObject jobject;
if (key instanceof String) {
jobject = new JSONObject();
jobject.put("key", key);
} else if (key instanceof JSONObject) {
jobject = (JSONObject) key;
} else {
throw new JSONException("An key in the extra-key matrix must be a string or an object");
}
return jobject;
}
public ExtraKeyButton[][] getMatrix() {
return buttons;
}
/**
* HashMap that implements Python dict.get(key, default) function.
* Default java.util .get(key) is then the same as .get(key, null);
*/
static class CleverMap<K,V> extends HashMap<K,V> {
V get(K key, V defaultValue) {
if (containsKey(key))
return get(key);
else
return defaultValue;
}
}
static class CharDisplayMap extends CleverMap<String, String> {}
/**
* Keys are displayed in a natural looking way, like "→" for "RIGHT"
*/
static final CharDisplayMap classicArrowsDisplay = new CharDisplayMap() {{
// classic arrow keys (for ◀ ▶ ▲ ▼ @see arrowVariationDisplay)
put("LEFT", ""); // U+2190 ← LEFTWARDS ARROW
put("RIGHT", ""); // U+2192 → RIGHTWARDS ARROW
put("UP", ""); // U+2191 ↑ UPWARDS ARROW
put("DOWN", ""); // U+2193 ↓ DOWNWARDS ARROW
}};
static final CharDisplayMap wellKnownCharactersDisplay = new CharDisplayMap() {{
// well known characters // https://en.wikipedia.org/wiki/{Enter_key, Tab_key, Delete_key}
put("ENTER", ""); // U+21B2 ↲ DOWNWARDS ARROW WITH TIP LEFTWARDS
put("TAB", ""); // U+21B9 ↹ LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR
put("BKSP", ""); // U+232B ⌫ ERASE TO THE LEFT sometimes seen and easy to understand
put("DEL", ""); // U+2326 ⌦ ERASE TO THE RIGHT not well known but easy to understand
put("DRAWER", ""); // U+2630 ☰ TRIGRAM FOR HEAVEN not well known but easy to understand
put("KEYBOARD", ""); // U+2328 ⌨ KEYBOARD not well known but easy to understand
}};
static final CharDisplayMap lessKnownCharactersDisplay = new CharDisplayMap() {{
// https://en.wikipedia.org/wiki/{Home_key, End_key, Page_Up_and_Page_Down_keys}
// home key can mean "goto the beginning of line" or "goto first page" depending on context, hence the diagonal
put("HOME", ""); // from IEC 9995 // U+21F1 ⇱ NORTH WEST ARROW TO CORNER
put("END", ""); // from IEC 9995 // ⇲ // U+21F2 ⇲ SOUTH EAST ARROW TO CORNER
put("PGUP", ""); // no ISO character exists, U+21D1 ⇑ UPWARDS DOUBLE ARROW will do the trick
put("PGDN", ""); // no ISO character exists, U+21D3 ⇓ DOWNWARDS DOUBLE ARROW will do the trick
}};
static final CharDisplayMap arrowTriangleVariationDisplay = new CharDisplayMap() {{
// alternative to classic arrow keys
put("LEFT", ""); // U+25C0 ◀ BLACK LEFT-POINTING TRIANGLE
put("RIGHT", ""); // U+25B6 ▶ BLACK RIGHT-POINTING TRIANGLE
put("UP", ""); // U+25B2 ▲ BLACK UP-POINTING TRIANGLE
put("DOWN", ""); // U+25BC ▼ BLACK DOWN-POINTING TRIANGLE
}};
static final CharDisplayMap notKnownIsoCharacters = new CharDisplayMap() {{
// Control chars that are more clear as text // https://en.wikipedia.org/wiki/{Function_key, Alt_key, Control_key, Esc_key}
// put("FN", "FN"); // no ISO character exists
put("CTRL", ""); // ISO character "U+2388 ⎈ HELM SYMBOL" is unknown to people and never printed on computers, however "U+25C7 ◇ WHITE DIAMOND" is a nice presentation, and "^" for terminal app and mac is often used
put("ALT", ""); // ISO character "U+2387 ⎇ ALTERNATIVE KEY SYMBOL'" is unknown to people and only printed as the Option key "⌥" on Mac computer
put("ESC", ""); // ISO character "U+238B ⎋ BROKEN CIRCLE WITH NORTHWEST ARROW" is unknown to people and not often printed on computers
}};
static final CharDisplayMap nicerLookingDisplay = new CharDisplayMap() {{
// nicer looking for most cases
put("-", ""); // U+2015 ― HORIZONTAL BAR
}};
/*
* Multiple maps are available to quickly change
* the style of the keys.
*/
/**
* Some classic symbols everybody knows
*/
private static final CharDisplayMap defaultCharDisplay = new CharDisplayMap() {{
putAll(classicArrowsDisplay);
putAll(wellKnownCharactersDisplay);
putAll(nicerLookingDisplay);
// all other characters are displayed as themselves
}};
/**
* Classic symbols and less known symbols
*/
private static final CharDisplayMap lotsOfArrowsCharDisplay = new CharDisplayMap() {{
putAll(classicArrowsDisplay);
putAll(wellKnownCharactersDisplay);
putAll(lessKnownCharactersDisplay); // NEW
putAll(nicerLookingDisplay);
}};
/**
* Only arrows
*/
private static final CharDisplayMap arrowsOnlyCharDisplay = new CharDisplayMap() {{
putAll(classicArrowsDisplay);
// putAll(wellKnownCharactersDisplay); // REMOVED
// putAll(lessKnownCharactersDisplay); // REMOVED
putAll(nicerLookingDisplay);
}};
/**
* Full Iso
*/
private static final CharDisplayMap fullIsoCharDisplay = new CharDisplayMap() {{
putAll(classicArrowsDisplay);
putAll(wellKnownCharactersDisplay);
putAll(lessKnownCharactersDisplay); // NEW
putAll(nicerLookingDisplay);
putAll(notKnownIsoCharacters); // NEW
}};
/**
* Some people might call our keys differently
*/
static private final CharDisplayMap controlCharsAliases = new CharDisplayMap() {{
put("ESCAPE", "ESC");
put("CONTROL", "CTRL");
put("RETURN", "ENTER"); // Technically different keys, but most applications won't see the difference
put("FUNCTION", "FN");
// no alias for ALT
// Directions are sometimes written as first and last letter for brevety
put("LT", "LEFT");
put("RT", "RIGHT");
put("DN", "DOWN");
// put("UP", "UP"); well, "UP" is already two letters
put("PAGEUP", "PGUP");
put("PAGE_UP", "PGUP");
put("PAGE UP", "PGUP");
put("PAGE-UP", "PGUP");
// no alias for HOME
// no alias for END
put("PAGEDOWN", "PGDN");
put("PAGE_DOWN", "PGDN");
put("PAGE-DOWN", "PGDN");
put("DELETE", "DEL");
put("BACKSPACE", "BKSP");
// easier for writing in termux.properties
put("BACKSLASH", "\\");
put("QUOTE", "\"");
put("APOSTROPHE", "'");
}};
CharDisplayMap getSelectedCharMap() {
switch (style) {
case "arrows-only":
return arrowsOnlyCharDisplay;
case "arrows-all":
return lotsOfArrowsCharDisplay;
case "all":
return fullIsoCharDisplay;
case "none":
return new CharDisplayMap();
default:
return defaultCharDisplay;
}
}
/**
* Applies the 'controlCharsAliases' mapping to all the strings in *buttons*
* Modifies the array, doesn't return a new one.
*/
public static String replaceAlias(String key) {
return controlCharsAliases.get(key, key);
}
}

View File

@@ -0,0 +1,382 @@
package com.termux.app.terminal.io.extrakeys;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.provider.Settings;
import android.util.AttributeSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ScheduledExecutorService;
import java.util.Map;
import java.util.HashMap;
import java.util.Arrays;
import java.util.stream.Collectors;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.GridLayout;
import android.widget.PopupWindow;
import com.termux.R;
import com.termux.view.TerminalView;
import androidx.drawerlayout.widget.DrawerLayout;
/**
* A view showing extra keys (such as Escape, Ctrl, Alt) not normally available on an Android soft
* keyboard.
*/
public final class ExtraKeysView extends GridLayout {
private static final int TEXT_COLOR = 0xFFFFFFFF;
private static final int BUTTON_COLOR = 0x00000000;
private static final int INTERESTING_COLOR = 0xFF80DEEA;
private static final int BUTTON_PRESSED_COLOR = 0xFF7F7F7F;
public ExtraKeysView(Context context, AttributeSet attrs) {
super(context, attrs);
}
static final Map<String, Integer> keyCodesForString = new HashMap<String, Integer>() {{
put("SPACE", KeyEvent.KEYCODE_SPACE);
put("ESC", KeyEvent.KEYCODE_ESCAPE);
put("TAB", KeyEvent.KEYCODE_TAB);
put("HOME", KeyEvent.KEYCODE_MOVE_HOME);
put("END", KeyEvent.KEYCODE_MOVE_END);
put("PGUP", KeyEvent.KEYCODE_PAGE_UP);
put("PGDN", KeyEvent.KEYCODE_PAGE_DOWN);
put("INS", KeyEvent.KEYCODE_INSERT);
put("DEL", KeyEvent.KEYCODE_FORWARD_DEL);
put("BKSP", KeyEvent.KEYCODE_DEL);
put("UP", KeyEvent.KEYCODE_DPAD_UP);
put("LEFT", KeyEvent.KEYCODE_DPAD_LEFT);
put("RIGHT", KeyEvent.KEYCODE_DPAD_RIGHT);
put("DOWN", KeyEvent.KEYCODE_DPAD_DOWN);
put("ENTER", KeyEvent.KEYCODE_ENTER);
put("F1", KeyEvent.KEYCODE_F1);
put("F2", KeyEvent.KEYCODE_F2);
put("F3", KeyEvent.KEYCODE_F3);
put("F4", KeyEvent.KEYCODE_F4);
put("F5", KeyEvent.KEYCODE_F5);
put("F6", KeyEvent.KEYCODE_F6);
put("F7", KeyEvent.KEYCODE_F7);
put("F8", KeyEvent.KEYCODE_F8);
put("F9", KeyEvent.KEYCODE_F9);
put("F10", KeyEvent.KEYCODE_F10);
put("F11", KeyEvent.KEYCODE_F11);
put("F12", KeyEvent.KEYCODE_F12);
}};
@SuppressLint("RtlHardcoded")
private void sendKey(View view, String keyName, boolean forceCtrlDown, boolean forceLeftAltDown) {
TerminalView terminalView = view.findViewById(R.id.terminal_view);
if ("KEYBOARD".equals(keyName)) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(0, 0);
} else if ("DRAWER".equals(keyName)) {
DrawerLayout drawer = view.findViewById(R.id.drawer_layout);
drawer.openDrawer(Gravity.LEFT);
} else if (keyCodesForString.containsKey(keyName)) {
Integer keyCode = keyCodesForString.get(keyName);
if (keyCode == null) return;
int metaState = 0;
if (forceCtrlDown) {
metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON;
}
if (forceLeftAltDown) {
metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON;
}
KeyEvent keyEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, metaState);
terminalView.onKeyDown(keyCode, keyEvent);
} else {
// not a control char
keyName.codePoints().forEach(codePoint -> {
terminalView.inputCodePoint(codePoint, forceCtrlDown, forceLeftAltDown);
});
}
}
private void sendKey(View view, ExtraKeyButton buttonInfo) {
if (buttonInfo.isMacro()) {
String[] keys = buttonInfo.getKey().split(" ");
boolean ctrlDown = false;
boolean altDown = false;
for (String key : keys) {
if ("CTRL".equals(key)) {
ctrlDown = true;
} else if ("ALT".equals(key)) {
altDown = true;
} else {
sendKey(view, key, ctrlDown, altDown);
ctrlDown = false;
altDown = false;
}
}
} else {
sendKey(view, buttonInfo.getKey(), false, false);
}
}
public enum SpecialButton {
CTRL, ALT, FN
}
private static class SpecialButtonState {
boolean isOn = false;
boolean isActive = false;
List<Button> buttons = new ArrayList<>();
void setIsActive(boolean value) {
isActive = value;
buttons.forEach(button -> button.setTextColor(value ? INTERESTING_COLOR : TEXT_COLOR));
}
}
private final Map<SpecialButton, SpecialButtonState> specialButtons = new HashMap<SpecialButton, SpecialButtonState>() {{
put(SpecialButton.CTRL, new SpecialButtonState());
put(SpecialButton.ALT, new SpecialButtonState());
put(SpecialButton.FN, new SpecialButtonState());
}};
private final Set<String> specialButtonsKeys = specialButtons.keySet().stream().map(Enum::name).collect(Collectors.toSet());
private boolean isSpecialButton(ExtraKeyButton button) {
return specialButtonsKeys.contains(button.getKey());
}
private ScheduledExecutorService scheduledExecutor;
private PopupWindow popupWindow;
private int longPressCount;
public boolean readSpecialButton(SpecialButton name) {
SpecialButtonState state = specialButtons.get(name);
if (state == null)
throw new RuntimeException("Must be a valid special button (see source)");
if (!state.isOn || !state.isActive)
return false;
state.setIsActive(false);
return true;
}
private Button createSpecialButton(String buttonKey, boolean needUpdate) {
SpecialButtonState state = specialButtons.get(SpecialButton.valueOf(buttonKey));
if (state == null) return null;
state.isOn = true;
Button button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
button.setTextColor(state.isActive ? INTERESTING_COLOR : TEXT_COLOR);
if (needUpdate) {
state.buttons.add(button);
}
return button;
}
void popup(View view, ExtraKeyButton extraButton) {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
Button button;
if (isSpecialButton(extraButton)) {
button = createSpecialButton(extraButton.getKey(), false);
if (button == null) return;
} else {
button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
button.setTextColor(TEXT_COLOR);
}
button.setText(extraButton.getDisplay());
button.setPadding(0, 0, 0, 0);
button.setMinHeight(0);
button.setMinWidth(0);
button.setMinimumWidth(0);
button.setMinimumHeight(0);
button.setWidth(width);
button.setHeight(height);
button.setBackgroundColor(BUTTON_PRESSED_COLOR);
popupWindow = new PopupWindow(this);
popupWindow.setWidth(LayoutParams.WRAP_CONTENT);
popupWindow.setHeight(LayoutParams.WRAP_CONTENT);
popupWindow.setContentView(button);
popupWindow.setOutsideTouchable(true);
popupWindow.setFocusable(false);
popupWindow.showAsDropDown(view, 0, -2 * height);
}
/**
* General util function to compute the longest column length in a matrix.
*/
static int maximumLength(Object[][] matrix) {
int m = 0;
for (Object[] row : matrix)
m = Math.max(m, row.length);
return m;
}
/**
* Reload the view given parameters in termux.properties
*
* @param infos matrix as defined in termux.properties extrakeys
* Can Contain The Strings CTRL ALT TAB FN ENTER LEFT RIGHT UP DOWN or normal strings
* Some aliases are possible like RETURN for ENTER, LT for LEFT and more (@see controlCharsAliases for the whole list).
* Any string of length > 1 in total Uppercase will print a warning
*
* Examples:
* "ENTER" will trigger the ENTER keycode
* "LEFT" will trigger the LEFT keycode and be displayed as "←"
* "→" will input a "→" character
* "" will input a "" character
* "-_-" will input the string "-_-"
*/
@SuppressLint("ClickableViewAccessibility")
public void reload(ExtraKeysInfo infos) {
if (infos == null)
return;
for(SpecialButtonState state : specialButtons.values())
state.buttons = new ArrayList<>();
removeAllViews();
ExtraKeyButton[][] buttons = infos.getMatrix();
setRowCount(buttons.length);
setColumnCount(maximumLength(buttons));
for (int row = 0; row < buttons.length; row++) {
for (int col = 0; col < buttons[row].length; col++) {
final ExtraKeyButton buttonInfo = buttons[row][col];
Button button;
if (isSpecialButton(buttonInfo)) {
button = createSpecialButton(buttonInfo.getKey(), true);
if (button == null) return;
} else {
button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
}
button.setText(buttonInfo.getDisplay());
button.setTextColor(TEXT_COLOR);
button.setPadding(0, 0, 0, 0);
final Button finalButton = button;
button.setOnClickListener(v -> {
if (Settings.System.getInt(getContext().getContentResolver(),
Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0) {
if (Build.VERSION.SDK_INT >= 28) {
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
} else {
// Perform haptic feedback only if no total silence mode enabled.
if (Settings.Global.getInt(getContext().getContentResolver(), "zen_mode", 0) != 2) {
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
}
}
}
View root = getRootView();
if (isSpecialButton(buttonInfo)) {
SpecialButtonState state = specialButtons.get(SpecialButton.valueOf(buttonInfo.getKey()));
if (state == null) return;
state.setIsActive(!state.isActive);
} else {
sendKey(root, buttonInfo);
}
});
button.setOnTouchListener((v, event) -> {
final View root = getRootView();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
longPressCount = 0;
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
if (Arrays.asList("UP", "DOWN", "LEFT", "RIGHT", "BKSP", "DEL").contains(buttonInfo.getKey())) {
// autorepeat
scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
scheduledExecutor.scheduleWithFixedDelay(() -> {
longPressCount++;
sendKey(root, buttonInfo);
}, 400, 80, TimeUnit.MILLISECONDS);
}
return true;
case MotionEvent.ACTION_MOVE:
if (buttonInfo.getPopup() != null) {
if (popupWindow == null && event.getY() < 0) {
if (scheduledExecutor != null) {
scheduledExecutor.shutdownNow();
scheduledExecutor = null;
}
v.setBackgroundColor(BUTTON_COLOR);
popup(v, buttonInfo.getPopup());
}
if (popupWindow != null && event.getY() > 0) {
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
popupWindow.dismiss();
popupWindow = null;
}
}
return true;
case MotionEvent.ACTION_CANCEL:
v.setBackgroundColor(BUTTON_COLOR);
if (scheduledExecutor != null) {
scheduledExecutor.shutdownNow();
scheduledExecutor = null;
}
return true;
case MotionEvent.ACTION_UP:
v.setBackgroundColor(BUTTON_COLOR);
if (scheduledExecutor != null) {
scheduledExecutor.shutdownNow();
scheduledExecutor = null;
}
if (longPressCount == 0 || popupWindow != null) {
if (popupWindow != null) {
popupWindow.setContentView(null);
popupWindow.dismiss();
popupWindow = null;
if (buttonInfo.getPopup() != null) {
if (isSpecialButton(buttonInfo.getPopup())) {
SpecialButtonState state = specialButtons.get(SpecialButton.valueOf(buttonInfo.getPopup().getKey()));
if (state == null) return true;
state.setIsActive(!state.isActive);
} else {
sendKey(root, buttonInfo.getPopup());
}
}
} else {
v.performClick();
}
}
return true;
default:
return true;
}
});
LayoutParams param = new GridLayout.LayoutParams();
param.width = 0;
param.height = 0;
param.setMargins(0, 0, 0, 0);
param.columnSpec = GridLayout.spec(col, GridLayout.FILL, 1.f);
param.rowSpec = GridLayout.spec(row, GridLayout.FILL, 1.f);
button.setLayoutParams(param);
addView(button);
}
}
}
}

View File

@@ -0,0 +1,156 @@
package com.termux.app.utils;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.Nullable;
import com.termux.R;
import com.termux.app.activities.ReportActivity;
import com.termux.shared.notification.NotificationUtils;
import com.termux.shared.file.FileUtils;
import com.termux.app.models.ReportInfo;
import com.termux.app.models.UserAction;
import com.termux.shared.settings.preferences.TermuxAppSharedPreferences;
import com.termux.shared.settings.preferences.TermuxPreferenceConstants;
import com.termux.shared.data.DataUtils;
import com.termux.shared.logger.Logger;
import com.termux.shared.termux.TermuxUtils;
import com.termux.shared.termux.TermuxConstants;
import java.nio.charset.Charset;
public class CrashUtils {
private static final String LOG_TAG = "CrashUtils";
/**
* Notify the user of a previous app crash by reading the crash info from the crash log file at
* {@link TermuxConstants#TERMUX_CRASH_LOG_FILE_PATH}. The crash log file would have been
* created by {@link com.termux.shared.crash.CrashHandler}.
*
* If the crash log file exists and is not empty and
* {@link TermuxPreferenceConstants.TERMUX_APP#KEY_CRASH_REPORT_NOTIFICATIONS_ENABLED} is
* enabled, then a notification will be shown for the crash on the
* {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME} channel, otherwise nothing will be done.
*
* After reading from the crash log file, it will be moved to {@link TermuxConstants#TERMUX_CRASH_LOG_BACKUP_FILE_PATH}.
*
* @param context The {@link Context} for operations.
* @param logTagParam The log tag to use for logging.
*/
public static void notifyCrash(final Context context, final String logTagParam) {
if (context == null) return;
TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(context);
// If user has disabled notifications for crashes
if (!preferences.getCrashReportNotificationsEnabled())
return;
new Thread() {
@Override
public void run() {
String logTag = DataUtils.getDefaultIfNull(logTagParam, LOG_TAG);
if (!FileUtils.regularFileExists(TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, false))
return;
String errmsg;
StringBuilder reportStringBuilder = new StringBuilder();
// Read report string from crash log file
errmsg = FileUtils.readStringFromFile(context, "crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, Charset.defaultCharset(), reportStringBuilder, false);
if (errmsg != null) {
Logger.logError(logTag, errmsg);
return;
}
// Move crash log file to backup location if it exists
FileUtils.moveRegularFile(context, "crash log", TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH, TermuxConstants.TERMUX_CRASH_LOG_BACKUP_FILE_PATH, true);
if (errmsg != null) {
Logger.logError(logTag, errmsg);
}
String reportString = reportStringBuilder.toString();
if (reportString == null || reportString.isEmpty())
return;
// Send a notification to show the crash log which when clicked will open the {@link ReportActivity}
// to show the details of the crash
String title = TermuxConstants.TERMUX_APP_NAME + " Crash Report";
Logger.logDebug(logTag, "The crash log file at \"" + TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH + "\" found. Sending \"" + title + "\" notification.");
Intent notificationIntent = ReportActivity.newInstance(context, new ReportInfo(UserAction.CRASH_REPORT, logTag, title, null, reportString, "\n\n" + TermuxUtils.getReportIssueMarkdownString(context), true));
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// Setup the notification channel if not already set up
setupCrashReportsNotificationChannel(context);
// Build the notification
Notification.Builder builder = getCrashReportsNotificationBuilder(context, title, null, null, pendingIntent, NotificationUtils.NOTIFICATION_MODE_VIBRATE);
if (builder == null) return;
// Send the notification
int nextNotificationId = NotificationUtils.getNextNotificationId(context);
NotificationManager notificationManager = NotificationUtils.getNotificationManager(context);
if (notificationManager != null)
notificationManager.notify(nextNotificationId, builder.build());
}
}.start();
}
/**
* Get {@link Notification.Builder} for {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID}
* and {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME}.
*
* @param context The {@link Context} for operations.
* @param title The title for the notification.
* @param notificationText The second line text of the notification.
* @param notificationBigText The full text of the notification that may optionally be styled.
* @param pendingIntent The {@link PendingIntent} which should be sent when notification is clicked.
* @param notificationMode The notification mode. It must be one of {@code NotificationUtils.NOTIFICATION_MODE_*}.
* @return Returns the {@link Notification.Builder}.
*/
@Nullable
public static Notification.Builder getCrashReportsNotificationBuilder(final Context context, final CharSequence title, final CharSequence notificationText, final CharSequence notificationBigText, final PendingIntent pendingIntent, final int notificationMode) {
Notification.Builder builder = NotificationUtils.geNotificationBuilder(context,
TermuxConstants.TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID, Notification.PRIORITY_HIGH,
title, notificationText, notificationBigText, pendingIntent, notificationMode);
if (builder == null) return null;
// Enable timestamp
builder.setShowWhen(true);
// Set notification icon
builder.setSmallIcon(R.drawable.ic_error_notification);
// Set background color for small notification icon
builder.setColor(0xFF607D8B);
// Dismiss on click
builder.setAutoCancel(true);
return builder;
}
/**
* Setup the notification channel for {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID} and
* {@link TermuxConstants#TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME}.
*
* @param context The {@link Context} for operations.
*/
public static void setupCrashReportsNotificationChannel(final Context context) {
NotificationUtils.setupNotificationChannel(context, TermuxConstants.TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_ID,
TermuxConstants.TERMUX_CRASH_REPORTS_NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
}
}

View File

@@ -0,0 +1,330 @@
package com.termux.app.utils;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.termux.R;
import com.termux.shared.notification.NotificationUtils;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_SERVICE;
import com.termux.app.activities.ReportActivity;
import com.termux.shared.logger.Logger;
import com.termux.shared.settings.preferences.TermuxAppSharedPreferences;
import com.termux.shared.settings.preferences.TermuxPreferenceConstants.TERMUX_APP;
import com.termux.shared.settings.properties.SharedProperties;
import com.termux.shared.settings.properties.TermuxPropertyConstants;
import com.termux.app.models.ReportInfo;
import com.termux.shared.models.ExecutionCommand;
import com.termux.app.models.UserAction;
import com.termux.shared.data.DataUtils;
import com.termux.shared.markdown.MarkdownUtils;
import com.termux.shared.termux.TermuxUtils;
public class PluginUtils {
/** Required file permissions for the executable file of execute intent. Executable file must have read and execute permissions */
public static final String PLUGIN_EXECUTABLE_FILE_PERMISSIONS = "r-x"; // Default: "r-x"
/** Required file permissions for the working directory of execute intent. Working directory must have read and write permissions.
* Execute permissions should be attempted to be set, but ignored if they are missing */
public static final String PLUGIN_WORKING_DIRECTORY_PERMISSIONS = "rwx"; // Default: "rwx"
private static final String LOG_TAG = "PluginUtils";
/**
* Process {@link ExecutionCommand} result.
*
* The ExecutionCommand currentState must be greater or equal to
* {@link ExecutionCommand.ExecutionState#EXECUTED}.
* If the {@link ExecutionCommand#isPluginExecutionCommand} is {@code true} and
* {@link ExecutionCommand#pluginPendingIntent} is not {@code null}, then the result of commands
* are sent back to the {@link PendingIntent} creator.
*
* @param context The {@link Context} that will be used to send result intent to the {@link PendingIntent} creator.
* @param logTag The log tag to use for logging.
* @param executionCommand The {@link ExecutionCommand} to process.
*/
public static void processPluginExecutionCommandResult(final Context context, String logTag, final ExecutionCommand executionCommand) {
if (executionCommand == null) return;
logTag = DataUtils.getDefaultIfNull(logTag, LOG_TAG);
if (!executionCommand.hasExecuted()) {
Logger.logWarn(logTag, "Ignoring call to processPluginExecutionCommandResult() since the execution command state is not higher than the ExecutionState.EXECUTED");
return;
}
Logger.logDebug(LOG_TAG, executionCommand.toString());
boolean result = true;
// If isPluginExecutionCommand is true and pluginPendingIntent is not null, then
// send pluginPendingIntent to its creator with the result
if (executionCommand.isPluginExecutionCommand && executionCommand.pluginPendingIntent != null) {
String errmsg = executionCommand.errmsg;
//Combine errmsg and stacktraces
if (executionCommand.isStateFailed()) {
errmsg = Logger.getMessageAndStackTracesString(executionCommand.errmsg, executionCommand.throwableList);
}
// Send pluginPendingIntent to its creator
result = sendPluginExecutionCommandResultPendingIntent(context, logTag, executionCommand.getCommandIdAndLabelLogString(), executionCommand.stdout, executionCommand.stderr, executionCommand.exitCode, executionCommand.errCode, errmsg, executionCommand.pluginPendingIntent);
}
if (!executionCommand.isStateFailed() && result)
executionCommand.setState(ExecutionCommand.ExecutionState.SUCCESS);
}
/**
* Process {@link ExecutionCommand} error.
*
* The ExecutionCommand currentState must be equal to {@link ExecutionCommand.ExecutionState#FAILED}.
* The {@link ExecutionCommand#errCode} must have been set to a value greater than
* {@link ExecutionCommand#RESULT_CODE_OK}.
* The {@link ExecutionCommand#errmsg} and any {@link ExecutionCommand#throwableList} must also
* be set with appropriate error info.
*
* If the {@link ExecutionCommand#isPluginExecutionCommand} is {@code true} and
* {@link ExecutionCommand#pluginPendingIntent} is not {@code null}, then the errors of commands
* are sent back to the {@link PendingIntent} creator.
*
* Otherwise if the {@link TERMUX_APP#KEY_PLUGIN_ERROR_NOTIFICATIONS_ENABLED} is
* enabled, then a flash and a notification will be shown for the error as well
* on the {@link TermuxConstants#TERMUX_PLUGIN_COMMAND_ERRORS_NOTIFICATION_CHANNEL_NAME} channel instead of just logging
* the error.
*
* @param context The {@link Context} for operations.
* @param logTag The log tag to use for logging.
* @param executionCommand The {@link ExecutionCommand} that failed.
* @param forceNotification If set to {@code true}, then a flash and notification will be shown
* regardless of if pending intent is {@code null} or
* {@link TERMUX_APP#KEY_PLUGIN_ERROR_NOTIFICATIONS_ENABLED}
* is {@code false}.
*/
public static void processPluginExecutionCommandError(final Context context, String logTag, final ExecutionCommand executionCommand, boolean forceNotification) {
if (context == null || executionCommand == null) return;
logTag = DataUtils.getDefaultIfNull(logTag, LOG_TAG);
if (!executionCommand.isStateFailed()) {
Logger.logWarn(logTag, "Ignoring call to processPluginExecutionCommandError() since the execution command is not in ExecutionState.FAILED");
return;
}
// Log the error and any exception
Logger.logStackTracesWithMessage(logTag, "(" + executionCommand.errCode + ") " + executionCommand.errmsg, executionCommand.throwableList);
// If isPluginExecutionCommand is true and pluginPendingIntent is not null, then
// send pluginPendingIntent to its creator with the errors
if (executionCommand.isPluginExecutionCommand && executionCommand.pluginPendingIntent != null) {
String errmsg = executionCommand.errmsg;
//Combine errmsg and stacktraces
if (executionCommand.isStateFailed()) {
errmsg = Logger.getMessageAndStackTracesString(executionCommand.errmsg, executionCommand.throwableList);
}
sendPluginExecutionCommandResultPendingIntent(context, logTag, executionCommand.getCommandIdAndLabelLogString(), executionCommand.stdout, executionCommand.stderr, executionCommand.exitCode, executionCommand.errCode, errmsg, executionCommand.pluginPendingIntent);
// No need to show notifications if a pending intent was sent, let the caller handle the result himself
if (!forceNotification) return;
}
TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(context);
// If user has disabled notifications for plugin, then just return
if (!preferences.getPluginErrorNotificationsEnabled() && !forceNotification)
return;
// Flash the errmsg
Logger.showToast(context, executionCommand.errmsg, true);
// Send a notification to show the errmsg which when clicked will open the {@link ReportActivity}
// to show the details of the error
String title = TermuxConstants.TERMUX_APP_NAME + " Plugin Execution Command Error";
StringBuilder reportString = new StringBuilder();
reportString.append(ExecutionCommand.getExecutionCommandMarkdownString(executionCommand));
reportString.append("\n\n").append(TermuxUtils.getAppInfoMarkdownString(context, true));
reportString.append("\n\n").append(TermuxUtils.getDeviceInfoMarkdownString(context));
Intent notificationIntent = ReportActivity.newInstance(context, new ReportInfo(UserAction.PLUGIN_EXECUTION_COMMAND, logTag, title, null, reportString.toString(), null,true));
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// Setup the notification channel if not already set up
setupPluginCommandErrorsNotificationChannel(context);
// Use markdown in notification
CharSequence notificationText = MarkdownUtils.getSpannedMarkdownText(context, executionCommand.errmsg);
//CharSequence notificationText = executionCommand.errmsg;
// Build the notification
Notification.Builder builder = getPluginCommandErrorsNotificationBuilder(context, title, notificationText, notificationText, pendingIntent, NotificationUtils.NOTIFICATION_MODE_VIBRATE);
if (builder == null) return;
// Send the notification
int nextNotificationId = NotificationUtils.getNextNotificationId(context);
NotificationManager notificationManager = NotificationUtils.getNotificationManager(context);
if (notificationManager != null)
notificationManager.notify(nextNotificationId, builder.build());
}
/**
* Send {@link ExecutionCommand} result {@link PendingIntent} in the
* {@link TERMUX_SERVICE#EXTRA_PLUGIN_RESULT_BUNDLE} bundle.
*
*
* @param context The {@link Context} that will be used to send result intent to the {@link PendingIntent} creator.
* @param logTag The log tag to use for logging.
* @param label The label of {@link ExecutionCommand}.
* @param stdout The stdout of {@link ExecutionCommand}.
* @param stderr The stderr of {@link ExecutionCommand}.
* @param exitCode The exitCode of {@link ExecutionCommand}.
* @param errCode The errCode of {@link ExecutionCommand}.
* @param errmsg The errmsg of {@link ExecutionCommand}.
* @param pluginPendingIntent The pluginPendingIntent of {@link ExecutionCommand}.
* @return Returns {@code true} if pluginPendingIntent was successfully send, otherwise [@code false}.
*/
public static boolean sendPluginExecutionCommandResultPendingIntent(Context context, String logTag, String label, String stdout, String stderr, Integer exitCode, Integer errCode, String errmsg, PendingIntent pluginPendingIntent) {
if (context == null || pluginPendingIntent == null) return false;
logTag = DataUtils.getDefaultIfNull(logTag, LOG_TAG);
Logger.logDebug(logTag, "Sending execution result for Execution Command \"" + label + "\" to " + pluginPendingIntent.getCreatorPackage());
String truncatedStdout = null;
String truncatedStderr = null;
String stdoutOriginalLength = (stdout == null) ? null: String.valueOf(stdout.length());
String stderrOriginalLength = (stderr == null) ? null: String.valueOf(stderr.length());
// Truncate stdout and stdout to max TRANSACTION_SIZE_LIMIT_IN_BYTES
if (stderr == null || stderr.isEmpty()) {
truncatedStdout = DataUtils.getTruncatedCommandOutput(stdout, DataUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES, false, false, false);
} else if (stdout == null || stdout.isEmpty()) {
truncatedStderr = DataUtils.getTruncatedCommandOutput(stderr, DataUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES, false, false, false);
} else {
truncatedStdout = DataUtils.getTruncatedCommandOutput(stdout, DataUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES / 2, false, false, false);
truncatedStderr = DataUtils.getTruncatedCommandOutput(stderr, DataUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES / 2, false, false, false);
}
if (truncatedStdout != null && truncatedStdout.length() < stdout.length()) {
Logger.logWarn(logTag, "Execution Result for Execution Command \"" + label + "\" stdout length truncated from " + stdoutOriginalLength + " to " + truncatedStdout.length());
stdout = truncatedStdout;
}
if (truncatedStderr != null && truncatedStderr.length() < stderr.length()) {
Logger.logWarn(logTag, "Execution Result for Execution Command \"" + label + "\" stderr length truncated from " + stderrOriginalLength + " to " + truncatedStderr.length());
stderr = truncatedStderr;
}
String errmsgOriginalLength = (errmsg == null) ? null: String.valueOf(errmsg.length());
// Truncate errmsg to max TRANSACTION_SIZE_LIMIT_IN_BYTES / 4
// trim from end to preserve start of stacktraces
String truncatedErrmsg = DataUtils.getTruncatedCommandOutput(errmsg, DataUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES / 4, true, false, false);
if (truncatedErrmsg != null && truncatedErrmsg.length() < errmsg.length()) {
Logger.logWarn(logTag, "Execution Result for Execution Command \"" + label + "\" errmsg length truncated from " + errmsgOriginalLength + " to " + truncatedErrmsg.length());
errmsg = truncatedErrmsg;
}
final Bundle resultBundle = new Bundle();
resultBundle.putString(TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE_STDOUT, stdout);
resultBundle.putString(TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE_STDOUT_ORIGINAL_LENGTH, stdoutOriginalLength);
resultBundle.putString(TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE_STDERR, stderr);
resultBundle.putString(TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE_STDERR_ORIGINAL_LENGTH, stderrOriginalLength);
if (exitCode != null) resultBundle.putInt(TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE_EXIT_CODE, exitCode);
if (errCode != null) resultBundle.putInt(TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE_ERR, errCode);
resultBundle.putString(TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE_ERRMSG, errmsg);
Intent resultIntent = new Intent();
resultIntent.putExtra(TERMUX_SERVICE.EXTRA_PLUGIN_RESULT_BUNDLE, resultBundle);
try {
pluginPendingIntent.send(context, Activity.RESULT_OK, resultIntent);
} catch (PendingIntent.CanceledException e) {
// The caller doesn't want the result? That's fine, just ignore
Logger.logDebug(logTag, "The Execution Command \"" + label + "\" creator " + pluginPendingIntent.getCreatorPackage() + " does not want the results anymore");
}
return true;
}
/**
* Get {@link Notification.Builder} for {@link TermuxConstants#TERMUX_PLUGIN_COMMAND_ERRORS_NOTIFICATION_CHANNEL_ID}
* and {@link TermuxConstants#TERMUX_PLUGIN_COMMAND_ERRORS_NOTIFICATION_CHANNEL_NAME}.
*
* @param context The {@link Context} for operations.
* @param title The title for the notification.
* @param notificationText The second line text of the notification.
* @param notificationBigText The full text of the notification that may optionally be styled.
* @param pendingIntent The {@link PendingIntent} which should be sent when notification is clicked.
* @param notificationMode The notification mode. It must be one of {@code NotificationUtils.NOTIFICATION_MODE_*}.
* @return Returns the {@link Notification.Builder}.
*/
@Nullable
public static Notification.Builder getPluginCommandErrorsNotificationBuilder(final Context context, final CharSequence title, final CharSequence notificationText, final CharSequence notificationBigText, final PendingIntent pendingIntent, final int notificationMode) {
Notification.Builder builder = NotificationUtils.geNotificationBuilder(context,
TermuxConstants.TERMUX_PLUGIN_COMMAND_ERRORS_NOTIFICATION_CHANNEL_ID, Notification.PRIORITY_HIGH,
title, notificationText, notificationBigText, pendingIntent, notificationMode);
if (builder == null) return null;
// Enable timestamp
builder.setShowWhen(true);
// Set notification icon
builder.setSmallIcon(R.drawable.ic_error_notification);
// Set background color for small notification icon
builder.setColor(0xFF607D8B);
// Dismiss on click
builder.setAutoCancel(true);
return builder;
}
/**
* Setup the notification channel for {@link TermuxConstants#TERMUX_PLUGIN_COMMAND_ERRORS_NOTIFICATION_CHANNEL_ID} and
* {@link TermuxConstants#TERMUX_PLUGIN_COMMAND_ERRORS_NOTIFICATION_CHANNEL_NAME}.
*
* @param context The {@link Context} for operations.
*/
public static void setupPluginCommandErrorsNotificationChannel(final Context context) {
NotificationUtils.setupNotificationChannel(context, TermuxConstants.TERMUX_PLUGIN_COMMAND_ERRORS_NOTIFICATION_CHANNEL_ID,
TermuxConstants.TERMUX_PLUGIN_COMMAND_ERRORS_NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
}
/**
* Check if {@link TermuxConstants#PROP_ALLOW_EXTERNAL_APPS} property is not set to "true".
*
* @param context The {@link Context} to get error string.
* @return Returns the {@code errmsg} if policy is violated, otherwise {@code null}.
*/
public static String checkIfRunCommandServiceAllowExternalAppsPolicyIsViolated(final Context context) {
String errmsg = null;
if (!SharedProperties.isPropertyValueTrue(context, TermuxPropertyConstants.getTermuxPropertiesFile(), TermuxConstants.PROP_ALLOW_EXTERNAL_APPS)) {
errmsg = context.getString(R.string.error_run_command_service_allow_external_apps_ungranted);
}
return errmsg;
}
}

View File

@@ -12,7 +12,7 @@ import android.provider.DocumentsProvider;
import android.webkit.MimeTypeMap;
import com.termux.R;
import com.termux.app.TermuxService;
import com.termux.shared.termux.TermuxConstants;
import java.io.File;
import java.io.FileNotFoundException;
@@ -22,7 +22,7 @@ import java.util.LinkedList;
/**
* A document provider for the Storage Access Framework which exposes the files in the
* $HOME/ folder to other apps.
* $HOME/ directory to other apps.
* <p/>
* Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent:
* <p/>
@@ -35,7 +35,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
private static final String ALL_MIME_TYPES = "*/*";
private static final File BASE_DIR = new File(TermuxService.HOME_PATH);
private static final File BASE_DIR = TermuxConstants.TERMUX_HOME_DIR;
// The default columns to return information about a root if no specific
@@ -63,15 +63,15 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
};
@Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
public Cursor queryRoots(String[] projection) {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
@SuppressWarnings("ConstantConditions") final String applicationName = getContext().getString(R.string.application_name);
final String applicationName = getContext().getString(R.string.application_name);
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
row.add(Root.COLUMN_SUMMARY, null);
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH);
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD);
row.add(Root.COLUMN_TITLE, applicationName);
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
@@ -91,9 +91,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
final File parent = getFileForDocId(parentDocumentId);
for (File file : parent.listFiles()) {
if (!file.getName().startsWith(".")) {
includeFile(result, null, file);
}
includeFile(result, null, file);
}
return result;
}
@@ -117,6 +115,29 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
return true;
}
@Override
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
File newFile = new File(parentDocumentId, displayName);
int noConflictId = 2;
while (newFile.exists()) {
newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")");
}
try {
boolean succeeded;
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
succeeded = newFile.mkdir();
} else {
succeeded = newFile.createNewFile();
}
if (!succeeded) {
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
}
} catch (IOException e) {
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
}
return newFile.getPath();
}
@Override
public void deleteDocument(String documentId) throws FileNotFoundException {
File file = getFileForDocId(documentId);
@@ -146,16 +167,15 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
final int MAX_SEARCH_RESULTS = 50;
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
final File file = pending.removeFirst();
// Avoid folders outside the $HOME folders linked in to symlinks (to avoid e.g. search
// Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g. search
// through the whole SD card).
boolean isInsideHome;
try {
isInsideHome = file.getCanonicalPath().startsWith(TermuxService.HOME_PATH);
isInsideHome = file.getCanonicalPath().startsWith(TermuxConstants.TERMUX_HOME_DIR_PATH);
} catch (IOException e) {
isInsideHome = true;
}
final boolean isHidden = file.getName().startsWith(".");
if (isInsideHome && !isHidden) {
if (isInsideHome) {
if (file.isDirectory()) {
Collections.addAll(pending, file.listFiles());
} else {
@@ -169,6 +189,11 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
return result;
}
@Override
public boolean isChildDocument(String parentDocumentId, String documentId) {
return documentId.startsWith(parentDocumentId);
}
/**
* Get the document id given a file. This document id must be consistent across time as other
* applications may save the ID and use it to reference documents later.
@@ -195,7 +220,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
final String name = file.getName();
final int lastDot = name.lastIndexOf('.');
if (lastDot >= 0) {
final String extension = name.substring(lastDot + 1);
final String extension = name.substring(lastDot + 1).toLowerCase();
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) return mime;
}
@@ -220,10 +245,11 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
int flags = 0;
if (file.isDirectory()) {
if (file.isDirectory() && file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
} else if (file.canWrite()) {
flags |= Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_DELETE;
flags |= Document.FLAG_SUPPORTS_WRITE;
}
if (file.getParentFile().canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE;
final String displayName = file.getName();
final String mimeType = getMimeType(file);

View File

@@ -2,17 +2,18 @@ package com.termux.filepicker;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.util.Log;
import android.util.Patterns;
import com.termux.R;
import com.termux.app.DialogUtils;
import com.termux.shared.interact.DialogUtils;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_SERVICE;
import com.termux.app.TermuxService;
import com.termux.shared.logger.Logger;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -22,12 +23,13 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;
public class TermuxFileReceiverActivity extends Activity {
static final String TERMUX_RECEIVEDIR = TermuxService.FILES_PATH + "/home/downloads";
static final String EDITOR_PROGRAM = TermuxService.HOME_PATH + "/bin/termux-file-editor";
static final String URL_OPENER_PROGRAM = TermuxService.HOME_PATH + "/bin/termux-url-opener";
static final String TERMUX_RECEIVEDIR = TermuxConstants.TERMUX_FILES_DIR_PATH + "/home/downloads";
static final String EDITOR_PROGRAM = TermuxConstants.TERMUX_HOME_DIR_PATH + "/bin/termux-file-editor";
static final String URL_OPENER_PROGRAM = TermuxConstants.TERMUX_HOME_DIR_PATH + "/bin/termux-url-opener";
/**
* If the activity should be finished when the name input dialog is dismissed. This is disabled
@@ -37,6 +39,13 @@ public class TermuxFileReceiverActivity extends Activity {
*/
boolean mFinishOnDismissNameDialog = true;
private static final String LOG_TAG = "TermuxFileReceiverActivity";
static boolean isSharedTextAnUrl(String sharedText) {
return Patterns.WEB_URL.matcher(sharedText).matches()
|| Pattern.matches("magnet:\\?xt=urn:btih:.*?", sharedText);
}
@Override
protected void onResume() {
super.onResume();
@@ -51,7 +60,7 @@ public class TermuxFileReceiverActivity extends Activity {
final Uri sharedUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (sharedText != null) {
if (Patterns.WEB_URL.matcher(sharedText).matches()) {
if (isSharedTextAnUrl(sharedText)) {
handleUrlAndFinish(sharedText);
} else {
String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
@@ -83,17 +92,7 @@ public class TermuxFileReceiverActivity extends Activity {
void showErrorDialogAndQuit(String message) {
mFinishOnDismissNameDialog = false;
new AlertDialog.Builder(this).setMessage(message).setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
}).setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
}).show();
new AlertDialog.Builder(this).setMessage(message).setOnDismissListener(dialog -> finish()).setPositiveButton(android.R.string.ok, (dialog, which) -> finish()).show();
}
void handleContentUri(final Uri uri, String subjectFromIntent) {
@@ -114,59 +113,45 @@ public class TermuxFileReceiverActivity extends Activity {
promptNameAndSave(in, attachmentFileName);
} catch (Exception e) {
showErrorDialogAndQuit("Unable to handle shared content:\n\n" + e.getMessage());
Log.e("termux", "handleContentUri(uri=" + uri + ") failed", e);
Logger.logStackTraceWithMessage(LOG_TAG, "handleContentUri(uri=" + uri + ") failed", e);
}
}
void promptNameAndSave(final InputStream in, final String attachmentFileName) {
DialogUtils.textInput(this, R.string.file_received_title, attachmentFileName, R.string.file_received_edit_button, new DialogUtils.TextSetListener() {
@Override
public void onTextSet(String text) {
File outFile = saveStreamWithName(in, text);
if (outFile == null) return;
DialogUtils.textInput(this, R.string.title_file_received, attachmentFileName, R.string.action_file_received_edit, text -> {
File outFile = saveStreamWithName(in, text);
if (outFile == null) return;
final File editorProgramFile = new File(EDITOR_PROGRAM);
if (!editorProgramFile.isFile()) {
showErrorDialogAndQuit("The following file does not exist:\n$HOME/bin/termux-file-editor\n\n"
+ "Create this file as a script or a symlink - it will be called with the received file as only argument.");
return;
}
final File editorProgramFile = new File(EDITOR_PROGRAM);
if (!editorProgramFile.isFile()) {
showErrorDialogAndQuit("The following file does not exist:\n$HOME/bin/termux-file-editor\n\n"
+ "Create this file as a script or a symlink - it will be called with the received file as only argument.");
return;
}
// Do this for the user if necessary:
//noinspection ResultOfMethodCallIgnored
editorProgramFile.setExecutable(true);
// Do this for the user if necessary:
//noinspection ResultOfMethodCallIgnored
editorProgramFile.setExecutable(true);
final Uri scriptUri = new Uri.Builder().scheme("file").path(EDITOR_PROGRAM).build();
final Uri scriptUri = new Uri.Builder().scheme("file").path(EDITOR_PROGRAM).build();
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE, scriptUri);
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
executeIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, new String[]{outFile.getAbsolutePath()});
startService(executeIntent);
finish();
}
Intent executeIntent = new Intent(TERMUX_SERVICE.ACTION_SERVICE_EXECUTE, scriptUri);
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
executeIntent.putExtra(TERMUX_SERVICE.EXTRA_ARGUMENTS, new String[]{outFile.getAbsolutePath()});
startService(executeIntent);
finish();
},
R.string.action_file_received_open_directory, text -> {
if (saveStreamWithName(in, text) == null) return;
Intent executeIntent = new Intent(TERMUX_SERVICE.ACTION_SERVICE_EXECUTE);
executeIntent.putExtra(TERMUX_SERVICE.EXTRA_WORKDIR, TERMUX_RECEIVEDIR);
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
startService(executeIntent);
finish();
},
R.string.file_received_open_folder_button, new DialogUtils.TextSetListener() {
@Override
public void onTextSet(String text) {
if (saveStreamWithName(in, text) == null) return;
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE);
executeIntent.putExtra(TermuxService.EXTRA_CURRENT_WORKING_DIRECTORY, TERMUX_RECEIVEDIR);
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
startService(executeIntent);
finish();
}
},
android.R.string.cancel, new DialogUtils.TextSetListener() {
@Override
public void onTextSet(final String text) {
finish();
}
}, new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (mFinishOnDismissNameDialog) finish();
}
android.R.string.cancel, text -> finish(), dialog -> {
if (mFinishOnDismissNameDialog) finish();
});
}
@@ -188,7 +173,7 @@ public class TermuxFileReceiverActivity extends Activity {
return outFile;
} catch (IOException e) {
showErrorDialogAndQuit("Error saving file:\n\n" + e);
Log.e("termux", "Error saving file", e);
Logger.logStackTraceWithMessage(LOG_TAG, "Error saving file", e);
return null;
}
}
@@ -207,9 +192,9 @@ public class TermuxFileReceiverActivity extends Activity {
final Uri urlOpenerProgramUri = new Uri.Builder().scheme("file").path(URL_OPENER_PROGRAM).build();
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE, urlOpenerProgramUri);
Intent executeIntent = new Intent(TERMUX_SERVICE.ACTION_SERVICE_EXECUTE, urlOpenerProgramUri);
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
executeIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, new String[]{url});
executeIntent.putExtra(TERMUX_SERVICE.EXTRA_ARGUMENTS, new String[]{url});
startService(executeIntent);
finish();
}

View File

@@ -1,10 +0,0 @@
package com.termux.terminal;
import android.util.Log;
public final class EmulatorDebug {
/** The tag to use with {@link Log}. */
public static final String LOG_TAG = "termux";
}

View File

@@ -1,458 +0,0 @@
package com.termux.terminal;
/**
* 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
};
// 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;
// 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;
}
// C0/C1 control characters
// Termux change: Return 0 instead of -1.
if (ucs < 32 || (0x07F <= ucs && ucs < 0x0A0)) return 0;
// combining characters with zero width
if (intable(ZERO_WIDTH, ucs)) return 0;
return intable(WIDE_EASTASIAN, ucs) ? 2 : 1;
}
/** The width at an index position in a java char array. */
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);
}
}

View File

@@ -1,37 +0,0 @@
package com.termux.view;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import com.termux.terminal.TerminalSession;
/**
* Input and scale listener which may be set on a {@link TerminalView} through
* {@link TerminalView#setOnKeyListener(TerminalKeyListener)}.
* <p/>
* TODO: Rename to TerminalViewClient.
*/
public interface TerminalKeyListener {
/** Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}. */
float onScale(float scale);
/** On a single tap on the terminal if terminal mouse reporting not enabled. */
void onSingleTapUp(MotionEvent e);
boolean shouldBackButtonBeMappedToEscape();
void copyModeChanged(boolean copyMode);
boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session);
boolean onKeyUp(int keyCode, KeyEvent e);
boolean readControlKey();
boolean readAltKey();
boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#E0E0E0" />
</shape>
</shape>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#212325" />
</shape>

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View File

@@ -0,0 +1,37 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<!--
Updated notification icon compliant with system icons guidelines
https://material.io/design/iconography/system-icons.html
-->
<group>
<clip-path
android:pathData="M0,0h24v24h-24z"/>
<path
android:pathData="M5,4H2L8,12L2,20H5L11,12L5,4Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M19.59,14
l-2.09,2.09
L15.41,14
L14,15.41
l2.09,2.09
L14,19.59
L15.41,21
l2.09,-2.08
L19.59,21
L21,19.59
l-2.08,-2.09
L21,15.41
L19.59,14
z"
android:fillColor="#ffffff"/>
</group>
</vector>

View File

@@ -0,0 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="108dp"
android:width="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<!-- Keep in sync with non-adaptive ic_launcher.xml -->
<path
android:fillColor="#FFFFFF"
android:pathData="M34,38
h6
l12,16
l-12,16
h-6
l12,-16
"
/>
<path
android:fillColor="#FFFFFF"
android:pathData="M56,66
h18
v4
h-18
"
/>
</vector>

View File

@@ -1,33 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="48dp"
android:width="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<!--
https://material.google.com/style/icons.html
Updated notification icon compliant with system icons guidelines
https://material.io/design/iconography/system-icons.html
-->
<!-- Screen border. -->
<path android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeWidth="3"
android:pathData="M7,4
l34,0
q3 0,3 3
l0,34
q0 3, -3 3
l-34,0
q-3 0, -3-3
l0 -34
q0 -3, 3 -3"
/>
<group>
<clip-path
android:pathData="M0,0h24v24h-24z"/>
<!-- Block cursor. -->
<path android:fillColor="#000"
android:pathData="M12,12
l5,0
l0,10
l-5,0"
/>
<path
android:pathData="M5,4H2L8,12L2,20H5L11,12L5,4Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M13,18H22V20H13V18Z"
android:fillColor="#ffffff"/>
</group>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FF000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_activated="true" android:drawable="@drawable/current_session_black"/>
<item android:state_activated="false" android:drawable="@drawable/session_ripple_black"/>
</selector>

View File

@@ -4,4 +4,4 @@
<item>
<color android:color="@android:color/white" />
</item>
</ripple>
</ripple>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@android:color/darker_gray" >
<item>
<color android:color="@android:color/background_dark" />
</item>
</ripple>

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
layout="@layout/partial_toolbar"
android:id="@+id/partial_toolbar"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingTop="@dimen/content_padding"
android:paddingBottom="36dip" />
</LinearLayout>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:fillViewport="true"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:scrollbarStyle="outsideInset">
<TextView
android:id="@+id/code_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/background_markdown_code_block"
android:fontFamily="monospace"
android:lineSpacingExtra="2dip"
android:paddingLeft="16dip"
android:paddingTop="8dip"
android:paddingRight="16dip"
android:paddingBottom="8dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="12sp" />
</HorizontalScrollView>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/default_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dip"
android:layout_marginRight="16dip"
android:breakStrategy="simple"
android:hyphenationFrequency="none"
android:lineSpacingExtra="2dip"
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#000"
android:textSize="12sp" />

View File

@@ -0,0 +1,9 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@@ -1,22 +1,27 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:orientation="vertical"
android:fitsSystemWindows="true">
<android.support.v4.widget.DrawerLayout
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_alignParentTop="true"
android:layout_above="@+id/viewpager"
android:layout_above="@+id/terminal_toolbar_view_pager"
android:layout_height="match_parent">
<com.termux.view.TerminalView
android:id="@+id/terminal_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="3dp"
android:layout_marginLeft="3dp"
android:focusableInTouchMode="true"
android:scrollbarThumbVertical="@drawable/terminal_scroll_shape"
android:scrollbars="vertical" />
android:scrollbars="vertical"
android:importantForAutofill="no"
android:autofillHints="password" />
<LinearLayout
android:id="@+id/left_drawer"
@@ -31,7 +36,7 @@
android:orientation="vertical">
<ListView
android:id="@+id/left_drawer_list"
android:id="@+id/terminal_sessions_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="top"
@@ -51,7 +56,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/toggle_soft_keyboard" />
android:text="@string/action_toggle_soft_keyboard" />
<Button
android:id="@+id/new_session_button"
@@ -59,17 +64,17 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/new_session" />
android:text="@string/action_new_session" />
</LinearLayout>
</LinearLayout>
</android.support.v4.widget.DrawerLayout>
</androidx.drawerlayout.widget.DrawerLayout>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
<androidx.viewpager.widget.ViewPager
android:id="@+id/terminal_toolbar_view_pager"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_height="37.5dp"
android:background="@android:drawable/screen_background_dark_transparent"
android:layout_alignParentBottom="true" />
</RelativeLayout>

View File

@@ -1,9 +1,9 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/row_line"
android:id="@+id/session_title"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:background="@drawable/selected_session_background"
android:background="@drawable/session_background_selected"
android:ellipsize="marquee"
android:gravity="start|center_vertical"
android:padding="6dip"
android:textSize="14sp" />
android:textSize="14sp" />

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimaryDark"
android:gravity="center_vertical"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:titleTextAppearance="@style/Toolbar.Title">
</androidx.appcompat.widget.Toolbar>
</LinearLayout>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<com.termux.app.ExtraKeysView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/extra_keys"
<com.termux.app.terminal.io.extrakeys.ExtraKeysView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/terminal_toolbar_extra_keys"
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@@ -1,13 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/text_input"
android:id="@+id/terminal_toolbar_text_input"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:imeOptions="actionSend|flagNoFullscreen"
android:maxLines="1"
android:inputType="text"
android:importantForAutofill="no"
android:textColor="@android:color/white"
android:textColorHighlight="@android:color/darker_gray"
android:paddingTop="0dp"
android:textCursorDrawable="@null"
android:paddingBottom="0dp"

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_item_share_report"
android:icon="@drawable/ic_share"
android:title="@string/action_share"
app:showAsAction="never" />
<item
android:id="@+id/menu_item_copy_report"
android:icon="@drawable/ic_copy"
android:title="@string/action_copy"
app:showAsAction="never" />
</menu>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/black"/>
<foreground android:drawable="@drawable/ic_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/black"/>
<foreground android:drawable="@drawable/ic_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 685 B

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Some files were not shown because too many files have changed in this diff Show More