├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Manual.md ├── README.md ├── TUTORIAL.md ├── doc_html ├── manual-dark.html.gz ├── manual-light.html.gz ├── tutorial-dark.html.gz └── tutorial-light.html.gz ├── doc_templates ├── doctemplate.html ├── github-markdown-dark.css └── github-markdown-light.css ├── examples └── yt-dl-gui ├── gendocHTML.sh ├── src ├── docviewer.sh ├── helper.c ├── tgui-bash.sh └── unzip.sh └── tutorial ├── dialog.sh ├── events.sh ├── hello_world.sh ├── image.sh ├── linearlayout.sh ├── scroll.sh └── widget.sh /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | Makefile 7 | cmake_install.cmake 8 | install_manifest.txt 9 | compile_commands.json 10 | CTestTestfile.cmake 11 | _deps 12 | 13 | 14 | /build 15 | /.idea 16 | /.vscode 17 | /termux-gui-bash.tar.gz 18 | /push.sh 19 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | project(termux-gui-bash) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | set(CMAKE_C_STANDARD_REQUIRED ON) 6 | set(CMAKE_C_FLAGS "-Os") 7 | set(THREADS_PREFER_PTHREAD_FLAG ON) 8 | 9 | set(TERMUX_PREFIX "${CMAKE_INSTALL_PREFIX}") 10 | 11 | find_package(Threads REQUIRED) 12 | 13 | set(version 1.0) 14 | 15 | 16 | 17 | add_executable(termux-gui-bash-helper src/helper.c) 18 | 19 | target_link_libraries(termux-gui-bash-helper Threads::Threads) 20 | 21 | 22 | configure_file(src/tgui-bash.sh tgui-bash.sh @ONLY) 23 | configure_file(src/unzip.sh tgui-bash @ONLY) 24 | configure_file(src/docviewer.sh docviewer.sh @ONLY) 25 | 26 | execute_process( 27 | COMMAND bash -c "gzip -c -k \"${CMAKE_CURRENT_BINARY_DIR}/tgui-bash.sh\" >> \"${CMAKE_CURRENT_BINARY_DIR}/tgui-bash\"" 28 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") 29 | 30 | install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/tgui-bash" TYPE BIN) 31 | install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/docviewer.sh" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/tgui-bash") 32 | install(FILES 33 | doc_html/tutorial-light.html.gz 34 | doc_html/tutorial-dark.html.gz 35 | doc_html/manual-light.html.gz 36 | doc_html/manual-dark.html.gz 37 | DESTINATION "${CMAKE_INSTALL_PREFIX}/share/tgui-bash") 38 | install(TARGETS termux-gui-bash-helper RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/libexec") 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Manual.md: -------------------------------------------------------------------------------- 1 | ## Name 2 | **tgui-bash** - Termux:GUI in bash 3 | 4 | ## Synopsis 5 | **tgui-bash** *filepath* ... 6 | **tgui-bash** -h ... 7 | source **tgui-bash** 8 | 9 | ## Description 10 | Sets up a connection to the Termux:GUI plugin through a small C helper, defines functions to interact with the plugin and finally sources the parameter into the script, so the functions are available. 11 | 12 | Can be used in a shebang (`#!/bin/tgui-bash`) or a script can self-exec (`! [ -v tgc_activity_tid ] && exec tgui-bash "${BASH_SOURCE[0]}" "$@"`). 13 | 14 | Using `set -eo pipefail` is advised to make your script exit when the connection to the plugin gets broken. 15 | For development, you should use `set -u` to make sure you spelled the constant names correctly. 16 | 17 | This library also uses the EXIT trap handler, so make sure to just add to it, but not overwrite it. 18 | 19 | ## Options 20 | `-h` Shows this manual or the tutorial in a browser of your choice. The following positional arguments are taken as answers to the questions. 21 | 22 | 23 | ## Environment 24 | All environment variables that affect bash will affect this program. 25 | 26 | 27 | ## Functions 28 | 29 | For passing arrays to functions and returning arrays, [bash namerefs](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameters.html) are used. 30 | Reference parameters are marked with & and accept a variable name. 31 | Colors are specified in RGBA in hex with `aabbggrr`. 32 | Boolean values use the literal text `true` and `false`, as they are directly used for the JSON messages to the plugin. 33 | Images should be in PNG or JPEG format and base64 encoded (`base64 -w 0 file` or `image_generating_command | base64 -w 0`). 34 | Public functions and variables start with `tg_`, private functions and variables with `tg__`. 35 | Private functions and variables can change between versions, public ones should be stable. 36 | 37 | 38 | More documentation for the functions as defined in the protocol is available [here](https://github.com/termux/termux-gui/blob/main/Protocol.md#protocol-methods). 39 | 40 | 41 | 42 | 43 | ### Global Functions 44 | 45 | 46 | 47 | | Name | Description | Parameters | Return code | 48 | |----------------------------|-------------------------------------------------------------|------------|---------------------------| 49 | | tg_global_turn_screen_on | Turns the screen on. | | | 50 | | tg_global_is_locked | Returns whether the screen is locked. | | 0: locked
1: unlocked | 51 | | tg_global_version | Returns the version code of the plugin to stdout. | | | 52 | | tg_msg_recv_event | Returns an event if one is available, else an empty string. | | | 53 | | tg_msg_recv_event_blocking | Waits for an event and returns it. | | | 54 | 55 | 56 | 57 | 58 | 59 | ### Activity Functions 60 | 61 | 62 | | Name | Description | Parameters | 63 | |-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 64 | | tg_activity_new | Creates a new Activity. See the `tgc_activity_*` constants for keys and their effect. |
  1. & Options associative array. See the constants for keys.
  2. & Return array. \[0] will contain the Activity id, \[1] will contain the Task id (if the Task id wasn't given as an option).
| 65 | | tg_activity_finish | Finishes an Activity. | The Activity id of the Activity. | 66 | | tg_activity_to_back | Moves the Task of an Activity to the back. | The Activity id of the Activity. | 67 | | tg_activity_theme | Sets the theme of the Activity. |
  1. The Activity id of the Activity.
  2. The status bar color.
  3. The primary color.
  4. The window background color.
  5. The text color.
  6. The accent color
| 68 | | tg_activity_description | Sets the Task description and icon. |
  1. The Activity id of the Activity.
  2. The description (appears unused).
  3. The icon.
| 69 | | tg_activity_pip_params | Sets the aspect ratio for picture-in-picture mode. |
  1. The Activity id of the Activity.
  2. The numerator of the aspect ratio.
  3. The denominator of the aspect ratio.
| 70 | | tg_activity_input | Sets how the Activity responds to the soft keyboard. "resize" resizes the Activity to fit the keyboard, "pan", pans the Activity upward. |
  1. The Activity id of the Activity.
  2. The response option.
| 71 | | tg_activity_pip | Makes an Activity enter or leave picture-in-picture-mode. |
  1. The Activity id of the Activity.
  2. Boolean: Whether the Activity should be in pip or not.
| 72 | | tg_activity_pip_auto | Set if an Activity should go into pip automatically if the user leaves. |
  1. The Activity id of the Activity.
  2. Boolean: Whether the Activity should go into pip automatically or not.
| 73 | | tg_activity_keep_screen_on | Sets if showing the Activity should keep the screen from turning off. |
  1. The Activity id of the Activity.
  2. Boolean: Whether screen should be kept on.
| 74 | | tg_activity_orientation | Sets the Orientation of the Activity. |
  1. The Activity id of the Activity.
  2. Orientation. Please see table ["Android Activity Orientation Table"](#android-activity-orientation-table) below for values.
| 75 | | tg_activity_position | Sets the screen position for an overlay Activity. |
  1. The Activity id of the Activity.
  2. The x position.
  3. The y position.
| 76 | | tg_activity_configuration | Gets the configuration for the Activity as a string. Get the values with the `tg_configuration_*` functions. |
  1. The Activity id of the Activity.
| 77 | | tg_activity_request_unlock | Requests the user to unlock the screen or unlocks it if the screen isn't protected. |
  1. The Activity id of the Activity.
| 78 | | tg_activity_hide_soft_keyboard | Hides the software keyboard. |
  1. The Activity id of the Activity.
| 79 | | tg_activity_intercept_back_button | Sets whether the back button should be intercepted. See the constant `tg_actvivity_intercept` fro more information. |
  1. The Activity id of the Activity.
  2. Boolean: Whether to intercept the back button.
| 80 | 81 | 82 | 83 | 84 | ### Task Functions 85 | 86 | 87 | 88 | | Name | Description | Parameters | 89 | |------------------|-----------------------------------------|--------------| 90 | | tg_task_finish | Finishes all activites in a Task. | The Task id. | 91 | | tg_task_to_front | Makes a Task visible to the user again. | The Task id. | 92 | 93 | 94 | 95 | ### Configuration Functions 96 | 97 | These functions all get the configuration string as the first parameter. 98 | 99 | | Name | Description | 100 | |---------------------------------|------------------------------------------------------------------------------------------| 101 | | tg_configuration_dark_mode | Whether dark mode is enabled, "true" or "false". "null" on Android versions prior to 10. | 102 | | tg_configuration_country | The country as a 2-letter string. | 103 | | tg_configuration_language | The language as a 2-letter string. | 104 | | tg_configuration_orientation | The screen orientation, either "landscape" or "portrait". | 105 | | tg_configuration_keyboardHidden | Whether a keyboard is currently available, as the string "true" or "false". | 106 | | tg_configuration_screenwidth | The current window width in dp. | 107 | | tg_configuration_screenheight | The current window height in dp. | 108 | | tg_configuration_fontscale | The current font scale value as a floating point number. | 109 | | tg_configuration_density | The display density as a float, such that screenwidth * density = screenwidth_in_px. | 110 | 111 | 112 | 113 | ### View Creation Functions 114 | 115 | All functions take the Activity id as the first parameter and 116 | a parameter associative array reference as the second parameter. 117 | The third parameter is an optional parent view id. 118 | For root Views, specify `""` as the third parameter or leave it out. 119 | The fourth parameter is the optional initial visibility of the View. 120 | The id of the created View is returned on stdout. 121 | The key for the parameter array are listed under the constants under `tgc_create_*`. 122 | 123 | 124 | | Name | Description | 125 | |-----------------------------|---------------------------------| 126 | | tg_create_linear | Creates a LinearLayout. | 127 | | tg_create_frame | Creates a FrameLayout. | 128 | | tg_create_swipe_refresh | Creates a SwipeRefreshLayout. | 129 | | tg_create_text | Creates a TextView. | 130 | | tg_create_edit | Creates an EditText. | 131 | | tg_create_button | Creates a Button. | 132 | | tg_create_image | Creates an ImageView. | 133 | | tg_create_space | Creates a Space. | 134 | | tg_create_nested_scroll | Creates a NestedScrollView. | 135 | | tg_create_horizontal_scroll | Creates a HorizontalScrollView. | 136 | | tg_create_radio | Creates a RadioButton. | 137 | | tg_create_radio_group | Creates a RadioGroup. | 138 | | tg_create_checkbox | Creates a Checkbox. | 139 | | tg_create_toggle | Creates a ToggleButton. | 140 | | tg_create_switch | Creates a Switch. | 141 | | tg_create_spinner | Creates a Spinner. | 142 | | tg_create_progress | Creates a ProgressBar. | 143 | | tg_create_tab | Creates a TabLayout. | 144 | | tg_create_grid | Creates a GridLayout. | 145 | | tg_create_web | Creates a WebView. | 146 | 147 | 148 | 149 | ### View Functions 150 | 151 | All these functions get the Activity id and the View id as the first and second parameters respectively. 152 | 153 | 154 | | Name | Description | Parameters | 155 | |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 156 | | tg_view_show_cursor | Sets whether or not a cursor is shown in an EditText. |
  1. Boolean. Whether to show the cursor or not.
| 157 | | tg_view_linear | Sets the LinearLayout parameters for a View in a LinearLayout. |
  1. The weight of the View.
  2. The position of the View.
| 158 | | tg_view_grid | Sets the GridLayout parameters for a View in a GridLayout. |
  1. The starting row of the View.
  2. The starting column of the View.
  3. The amount of rows this View uses.
  4. The amount of columns this View uses
  5. The alignment of the View in the row.
  6. The alignment of the View in the column.
| 159 | | tg_view_location | Sets position of a View. |
  1. The x position.
  2. The y position
  3. Whether to position is in dp or not.
  4. Whether the View should be on top of al others.
| 160 | | tg_view_vis | Sets the visibility of a Vew. |
  1. The visibility of the View.
| 161 | | tg_view_width | Sets the width of the view. |
  1. The width of the View.
  2. Whether the width is in px or not.
| 162 | | tg_view_height | Sets the height of the view. |
  1. The height of the View.
  2. Whether the height is in px or not.
| 163 | | tg_view_dimensions | Gets the current with and height of a View in pixels. | | 164 | | tg_view_delete | Deletes a View and its children from the Layout hierarchy. | | 165 | | tg_view_delete_children | Deletes the children of a View from the Layout hierarchy. | | 166 | | tg_view_margin | Sets the margin of a View. |
  1. The margin in dp.
  2. The direction of the margin. Leave empty for all directions.
| 167 | | tg_view_padding | Sets the padding of a View. |
  1. The padding in dp.
  2. The direction of the padding. Leave empty for all directions.
| 168 | | tg_view_bg_color | Sets the background color of a View. |
  1. The color.
| 169 | | tg_view_text_color | Sets the text color of a View. |
  1. The color
| 170 | | tg_view_progress | Sets the progress of a ProgressBar. |
  1. The progress in range from 0 to 100.
| 171 | | tg_view_refreshing | Sets whether a SwipeRefreshLayout is refreshing. |
  1. Whether the SwipeRefreshLayout is refreshing.
| 172 | | tg_view_text | Sets the text of the View. |
  1. The text.
| 173 | | tg_view_gravity | Sets the gravity of the text of the View. |
  1. The horizontal gravity.
  2. The vertical gravity
| 174 | | tg_view_text_size | Sets the text size. |
  1. The text size in sp.
| 175 | | tg_view_get_text | gets the text of the View. | | 176 | | tg_view_checked | Sets a RadioButton, CheckBox, Switch or ToggleButton to checked or unchecked explicitly. |
  1. Whether the View is checked.
| 177 | | tg_view_request_focus | Focuses a View and opens the soft keyboard if the View has Keyboard input. |
  1. Whether the soft keyboard should be forced to open.
| 178 | | tg_view_get_scroll | Gets the x and y scroll position of an NestedScrollView or HorizontalScrollView. | | 179 | | tg_view_set_scroll | Sets the x and y scroll position of an NestedScrollView or HorizontalScrollView. |
  1. The x scroll position.
  2. The y scroll position.
  3. Whether the srcoll should be smooth.
| 180 | | tg_view_list | Set the list of a Spinner or TabLayout. |
  1. & The array of tab/item names.
| 181 | | tg_view_image | Sets the image for an ImageView. |
  1. The image as a base64 encoded string.
| 182 | | tg_view_select_tab | Selects a Tab in a TabLayout. The corresponding itemselected event is also send. |
  1. The tab index, starting from 0.
| 183 | | tg_view_select_item | Selects a item in a Spinner. The corresponding itemselected event is also send. |
  1. The item index, starting from 0.
| 184 | | tg_view_clickable | Sets whether a View can be clicked by the user (if yes, emits a sound and animation when clicked and sends a click event (if click events are enabled))). |
  1. Whether the View should be clickable.
| 185 | 186 | 187 | ### Remote Layout, Widget & Notification Functions 188 | 189 | 190 | 191 | | Name | Description | Parameters | 192 | |---------------------------|-------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------| 193 | | tg_remote_create_layout | Creates a new remote Layout. Returns the id for the remote Layout | | 194 | | tg_remote_delete_layout | Deletes a remote Layout. |
  1. The id of the Layout to delete.
| 195 | | tg_remote_create_frame | Creates a FrameLayout in a remote Layout. Returns the View id in the Layout. |
  1. The id of the Layout.
  2. Optional. The parent if for the View.
| 196 | | tg_remote_create_text | Creates a TextView in a remote Layout. Returns the View id in the Layout. |
  1. The id of the Layout.
  2. Optional. The parent if for the View.
| 197 | | tg_remote_create_button | Creates a Button in a remote Layout. Returns the View id in the Layout. |
  1. The id of the Layout.
  2. Optional. The parent if for the View.
| 198 | | tg_remote_create_image | Creates an ImageView in a remote Layout. Returns the View id in the Layout. |
  1. The id of the Layout.
  2. Optional. The parent if for the View.
| 199 | | tg_remote_create_progress | Creates a ProgressBar in a remote Layout. Returns the View id in the Layout. |
  1. The id of the Layout.
  2. Optional. The parent if for the View.
| 200 | | tg_remote_bg_color | Sets the background color for a View in a remote Layout. |
  1. The id of the Layout.
  2. The id of the View.
  3. The background color.
| 201 | | tg_remote_progress | Sets values for a ProgressBar in a remote Layout. |
  1. The id of the Layout.
  2. The id of the View.
  3. The progress between 0 and 100.
| 202 | | tg_remote_text | Sets the text of a remote View. |
  1. The id of the Layout.
  2. The id of the View.
  3. The text.
| 203 | | tg_remote_text_size | Sets the text size of a remote View. |
  1. The id of the Layout.
  2. The id of the View.
  3. The text size.
| 204 | | tg_remote_text_color | Sets the Text color for a View in a remote Layout. |
  1. The id of the Layout.
  2. The id of the View.
  3. The text color.
| 205 | | tg_remote_vis | Sets the visibility for a View in a remote Layout. |
  1. The id of the Layout.
  2. The id of the View.
  3. The View visibility.
| 206 | | tg_remote_padding | Sets the padding in pixels for a View in a remote Layout. |
  1. The id of the Layout.
  2. The id of the View.
  3. The padding.
| 207 | | tg_remote_image | Sets the image of a remote ImageView. |
  1. The id of the Layout.
  2. The id of the View.
  3. The image.
| 208 | | tg_widget_layout | Sets the layout of a Widget to a remote Layout. |
  1. The id of the Layout.
  2. The id of the Widget.
| 209 | | tg_not_create_channel | On Android 8.0 and higher this creates a NotificationChannel, if one with the given id doesn't exist. |
  1. The id string of the channel.
  2. The importance of the channel.
  3. The user-visible name.
| 210 | | tg_not_create | Creates a notification. Returns the notification id used for further calls. |
  1. The channel id.
  2. The notification importance
  3. & The parameter array. See the `tgc_not_*` constants.
| 211 | | tg_not_cancel | Cancels a notification. |
  1. The id of the Notification to cancel.
| 212 | 213 | 214 | 215 | ### Event Functions 216 | 217 | These functions change whether specific events are send for Views. 218 | The first parameter is the Activity id of the View, the second the id of the View 219 | and the third is the boolean for whether to send the event or not. 220 | 221 | | Name | 222 | |--------------------------| 223 | | tg_event_send_click | 224 | | tg_event_send_long_click | 225 | | tg_event_send_focus | 226 | | tg_event_send_touch | 227 | | tg_event_send_text | 228 | 229 | 230 | ### Event Parsing Functions 231 | 232 | Events are send as JSON, and are thus not usable directly in Bash. 233 | That's where the dependency on `jq` comes in: it is used to parse the events. 234 | You can use `jq` on the event data directly, but for some common tasks there are utility functions. 235 | They all get the event data as their only parameter. 236 | For more information about events, see [the protocol specification](https://github.com/termux/termux-gui/blob/main/Protocol.md#events). 237 | 238 | | Name | Description | 239 | |----------------|------------------------------------------------------------------------------------------------| 240 | | tg_event_type | Returns the event type, which can then be compared to one of the `tgc_ev_*` constants. | 241 | | tg_event_value | Returns the event value, which is a JSON object for some events and a direct value for others. | 242 | | tg_event_aid | Returns the Activity id of the event, or "null" if not present. | 243 | | tg_event_id | Returns the View id of the event, or "null" if not present. | 244 | 245 | ### WebView Functions 246 | 247 | All these functions get the Activity id and the View id as the first and second parameters respectively. 248 | 249 | | Name | Description | Parameters | 250 | |-------------------------|--------------------------------------------------------------------|--------------------------------------------------------------| 251 | | tg_web_allow_js | Sets whether Javascript is allowed to run in the WebView. |
  1. Boolean.Whether to allow or not.
| 252 | | tg_web_allow_content | Sets whether content URIs are allowed in the WebView. |
  1. Boolean.Whether to allow or not.
| 253 | | tg_web_set_data | Sets the document. |
  1. The data.
| 254 | | tg_web_load_uri | Loads a specific URI. |
  1. The URI.
| 255 | | tg_web_allow_navigation | Sets whether user/Javascript navigation is allowed in the WebView. |
  1. Boolean.Whether to allow or not.
| 256 | | tg_web_back | Goes back in the history. | | 257 | | tg_web_forward | Goes forward in the history. | | 258 | | tg_web_eval_js | Runs Javascript. |
  1. The Javascript.
| 259 | 260 | ## Constants 261 | 262 | 263 | 264 | #### tgc_activity_tid="tid" 265 | Key for the `tg_activity_new` options array. The value is a number. 266 | Specify a Task id here if you want Activities to launch over each other in the same Task. 267 | 268 | #### tgc_activity_dialog="dialog" 269 | Key for the `tg_activity_new` options array. The value is a boolean. 270 | Set this to make the Activity a dialog. 271 | 272 | #### tgc_activity_canceloutside="canceloutside" 273 | Key for the `tg_activity_new` options array. The value is a boolean. 274 | Set this to false if you want your dialog to not be dismissed when the user taps on something else. 275 | 276 | #### tgc_activity_pip="pip" 277 | Key for the `tg_activity_new` options array. The value is a boolean. 278 | Set this to let the Activity start in picture-in-picture mode. 279 | 280 | #### tgc_activity_lockscreen="lockscreen" 281 | Key for the `tg_activity_new` options array. The value is a boolean. 282 | Set this to make the Activity stay visible and interactable on the lockscreen. 283 | Make sure your interface is secure in this case, to not allow arbitrary command execution or file I/O. 284 | 285 | #### tgc_activity_overlay="overlay" 286 | Key for the `tg_activity_new` options array. The value is a boolean. 287 | This launches the Activity as an overlay over everything else, similar to picture-in-picture mode, but you can interact with all Views. 288 | 289 | #### tgc_activity_intercept="intercept" 290 | Key for the `tg_activity_new` options array. The value is a boolean. 291 | This option makes the back button send an event instead of finishing the Activity. 292 | 293 | 294 | #### tgc_create_text="text" 295 | Key for the `tg_create_*` parameter array. 296 | For Button, TextView and EditText, this is the initial Text. 297 | 298 | #### tgc_create_selectable_text="selectableText" 299 | 300 | Key for the `tg_create_*` parameter array. 301 | For TextViews, this specifies whether the text can be selected. Default is false. 302 | 303 | #### tgc_create_clickable_links="clickableLinks" 304 | 305 | Key for the `tg_create_*` parameter array. 306 | For TextViews, this specifies whether links can be clicked or not. Default is false. 307 | 308 | #### tgc_create_vertical="vertical" 309 | 310 | Key for the `tg_create_*` parameter array. 311 | For LinearLayout, this specifies if the Layout is vertical or horizontal. 312 | If not specified, vertical is assumed. 313 | 314 | #### tgc_create_snapping="snapping" 315 | 316 | Key for the `tg_create_*` parameter array. 317 | NestedScrollView and HorizontalScrollView snap to the nearest item if this is set to true. 318 | Default is false. 319 | 320 | #### tgc_create_fill_viewport="fillviewport" 321 | 322 | Key for the `tg_create_*` parameter array. 323 | Makes the child of a HorizontalScrollView or a NestedScrollView automatically expand to the ScrollView size. 324 | Default is false. 325 | 326 | #### tgc_create_no_bar="nobar" 327 | 328 | Key for the `tg_create_*` parameter array. 329 | Hides the scroll bar for HorizontalScrollView and NestedScrollView. 330 | Default is false. 331 | 332 | #### tgc_create_checked="checked" 333 | 334 | Key for the `tg_create_*` parameter array. 335 | Whether a RadioButton, CheckBox, Switch or ToggleButton should be checked. 336 | Defaults to false. 337 | 338 | #### tgc_create_single_line="singleline" 339 | 340 | Key for the `tg_create_*` parameter array. 341 | Whether an EditText should enable multiple lines to be entered. 342 | 343 | #### tgc_create_line="line" 344 | 345 | Key for the `tg_create_*` parameter array. 346 | Whether the line below an EditText should be shown. 347 | 348 | #### tgc_create_type="type" 349 | 350 | Key for the `tg_create_*` parameter array. 351 | For EditText this specifies the [input type](https://developer.android.com/reference/android/widget/TextView#attr_android:inputType): can be one of "text", "textMultiLine", "phone", "date", "time", "datetime", "number", "numberDecimal", "numberPassword", "numberSigned", "numberDecimalSigned", "textEmailAddress", "textPassword". "text" is the default. Specifying singleline as true sets this to "text". 352 | 353 | #### tgc_create_rows="rows" 354 | 355 | Key for the `tg_create_*` parameter array. 356 | Row count for GridLayout. 357 | 358 | #### tgc_create_cols="cols" 359 | 360 | Key for the `tg_create_*` parameter array. 361 | Column count for GridLayout. 362 | 363 | #### tgc_create_all_caps="allcaps" 364 | 365 | Key for the `tg_create_*` parameter array. 366 | Use this when creating a button to make all text automatically all caps (using small caps if possible). 367 | 368 | #### tgc_vis_gone="0" 369 | 370 | Visibility constant for Views. This makes Views invisible and take up no space in the Layout. 371 | 372 | #### tgc_vis_visible="2" 373 | 374 | Visibility constant for Views. This makes Views visible. 375 | #### tgc_vis_hidden="1" 376 | 377 | Visibility constant for Views. This makes Views invisible, but still take up space in the Layout. 378 | 379 | #### tgc_view_wrap_content='"WRAP_CONTENT"' 380 | 381 | Size constant for Views. This makes Views take as much space as needed for their content. 382 | 383 | #### tgc_view_match_parent='"MATCH_PARENT"' 384 | 385 | Size constant for Views. This makes Views take as much space as their parent. 386 | 387 | #### tgc_grid_top="top" 388 | 389 | Alignment for Views in GridLayout. 390 | 391 | #### tgc_grid_bottom="bottom" 392 | 393 | Alignment for Views in GridLayout. 394 | 395 | #### tgc_grid_left="left" 396 | 397 | Alignment for Views in GridLayout. 398 | 399 | #### tgc_grid_right="right" 400 | 401 | Alignment for Views in GridLayout. 402 | 403 | #### tgc_grid_center="center" 404 | 405 | Alignment for Views in GridLayout. 406 | 407 | #### tgc_grid_baseline="baseline" 408 | 409 | Alignment for Views in GridLayout. 410 | 411 | #### tgc_grid_fill="fill" 412 | 413 | Alignment for Views in GridLayout. 414 | 415 | #### tgc_dir_top="top" 416 | 417 | Direction constant. 418 | 419 | #### tgc_dir_bottom="bottom" 420 | 421 | Direction constant. 422 | 423 | #### tgc_dir_left="left" 424 | 425 | Direction constant. 426 | 427 | #### tgc_dir_right="right" 428 | 429 | Direction constant. 430 | 431 | #### tgc_grav_top_left="0" 432 | 433 | Text gravity constant. This makes text gravitate to the top/left. 434 | 435 | #### tgc_grav_center="1" 436 | 437 | Text gravity constant. This makes text gravitate to the center. 438 | 439 | #### tgc_grav_bottom_right="2" 440 | 441 | Text gravity constant. This makes text gravitate to the bottom/right. 442 | 443 | #### tgc_not_id="id" 444 | 445 | Key for the `tg_not_create` parameter array. 446 | The id of the notification to update. If not specified, generates a new id. 447 | 448 | #### tgc_not_ongoing="ongoing" 449 | 450 | Key for the `tg_not_create` parameter array. 451 | If true, the notification cannot be dismissed by the user, 452 | but the notification is automatically dismissed when you close the connection to the plugin. 453 | 454 | #### tgc_not_layout="layout" 455 | 456 | Key for the `tg_not_create` parameter array. 457 | The id of the remote Layout to use. 458 | 459 | #### tgc_not_expanded_layout="expandedLayout" 460 | 461 | Key for the `tg_not_create` parameter array. 462 | The id of the remote Layout to use when the notification has been expanded. 463 | 464 | #### tgc_not_hud_layout="hudLayout" 465 | 466 | Key for the `tg_not_create` parameter array. 467 | The id of the remote Layout to use when the notification is shown as a head-up notification. 468 | 469 | #### tgc_not_title="title" 470 | 471 | Key for the `tg_not_create` parameter array. 472 | The notification title. 473 | 474 | #### tgc_not_content="content" 475 | 476 | Key for the `tg_not_create` parameter array. 477 | The notification content text. 478 | 479 | #### tgc_not_large_image="largeImage" 480 | 481 | Key for the `tg_not_create` parameter array. 482 | A large image to display in the expanded view. You can only set either largeImage or largeText. 483 | 484 | #### tgc_not_large_text="largeText" 485 | 486 | Key for the `tg_not_create` parameter array. 487 | A large block of text to display in the expanded view. HTML formatting is supported. 488 | You can only set either largeImage or largeText. 489 | 490 | #### tgc_not_large_image_thumbnail="largeImageAsThumbnail" 491 | 492 | Key for the `tg_not_create` parameter array. 493 | If true, the largeImage is shown as a thumbnail in the collapsed view. 494 | 495 | #### tgc_not_icon="icon" 496 | 497 | Key for the `tg_not_create` parameter array. 498 | An image for the Notification in PNG or JPEG. 499 | Defaults to the Termux:GUI app icon if left empty. 500 | 501 | #### tgc_not_alert_once="alertOnce" 502 | 503 | Key for the `tg_not_create` parameter array. 504 | If this call is used to update a notification, don't alert the user again. 505 | 506 | #### tgc_not_show_timestamp="showTimestamp" 507 | 508 | Key for the `tg_not_create` parameter array. 509 | Shows the timestamp of the notification. 510 | 511 | #### tgc_not_timestamp="timestamp" 512 | 513 | Key for the `tg_not_create` parameter array. 514 | The timestamp to use in form of milliseconds since start of the epoch. 515 | 516 | #### tgc_not_actions="actions" 517 | 518 | Key for the `tg_not_create` parameter array. 519 | An array of strings with the names of actions. 520 | Pressing actions will generate a notificationaction event. 521 | 522 | ### Event Constants 523 | 524 | For more information about events, see [the protocol specification](https://github.com/termux/termux-gui/blob/main/Protocol.md#events). 525 | 526 | #### tgc_ev_click="click" 527 | 528 | 529 | #### tgc_ev_long_click="longClick" 530 | 531 | 532 | #### tgc_ev_focus_change="focusChange" 533 | 534 | 535 | #### tgc_ev_refresh="refresh" 536 | 537 | 538 | #### tgc_ev_selected="selected" 539 | 540 | 541 | #### tgc_ev_item_selected="itemselected" 542 | 543 | 544 | #### tgc_ev_text="text" 545 | 546 | 547 | #### tgc_ev_back="back" 548 | 549 | 550 | #### tgc_ev_webview_navigation="webviewNavigation" 551 | 552 | 553 | #### tgc_ev_webview_http_error="webviewHTTPError" 554 | 555 | 556 | #### tgc_ev_webview_error="webviewError" 557 | 558 | 559 | #### tgc_ev_webview_destroyed="webviewDestroyed" 560 | 561 | 562 | #### tgc_ev_webview_progress="webviewProgress" 563 | 564 | 565 | #### tgc_ev_webview_console_message="webviewConsoleMessage" 566 | 567 | 568 | #### tgc_ev_create="create" 569 | 570 | 571 | #### tgc_ev_start="start" 572 | 573 | 574 | #### tgc_ev_resume="resume" 575 | 576 | 577 | #### tgc_ev_pause="pause" 578 | 579 | 580 | #### tgc_ev_stop="stop" 581 | 582 | 583 | #### tgc_ev_destroy="destroy" 584 | 585 | 586 | #### tgc_ev_config="config" 587 | 588 | 589 | #### tgc_ev_user_leave_hint="UserLeaveHint" 590 | 591 | 592 | #### tgc_ev_pip_changed="pipchanged" 593 | 594 | 595 | #### tgc_ev_airplane="airplane" 596 | 597 | 598 | #### tgc_ev_locale="locale" 599 | 600 | 601 | #### tgc_ev_screen_on="screen_on" 602 | 603 | 604 | #### tgc_ev_screen_off="screen_off" 605 | 606 | 607 | #### tgc_ev_timezone="timezone" 608 | 609 | 610 | #### tgc_ev_notification="notification" 611 | 612 | 613 | #### tgc_ev_notification_dismissed="notificationDismissed" 614 | 615 | 616 | #### tgc_ev_notification_action="notificationaction" 617 | 618 | 619 | #### tgc_ev_remote_click="remoteclick" 620 | 621 | 622 | 623 | 624 | 625 | ## External Resources 626 | 627 | 628 | 629 | ### [Android Activity Orientation Table](https://developer.android.com/reference/android/R.attr#screenOrientation) 630 | 631 | | Constant | Description | 632 | |:-----------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 633 | | behind | Keep the screen in the same orientation as whatever is behind this activity. Corresponds to ActivityInfo.SCREEN_ORIENTATION_BEHIND. | 634 | | fullSensor | Orientation is determined by a physical orientation sensor: the display will rotate based on how the user moves the device. This allows any of the 4 possible rotations, regardless of what the device will normally do (for example some devices won't normally use 180 degree rotation). Corresponds to ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR. | 635 | | fullUser | Respect the user's sensor-based rotation preference, but if sensor-based rotation is enabled then allow the screen to rotate in all 4 possible directions regardless of what the device will normally do (for example some devices won't normally use 180 degree rotation). Corresponds to ActivityInfo.SCREEN_ORIENTATION_FULL_USER. | 636 | | landscape | Would like to have the screen in a landscape orientation: that is, with the display wider than it is tall, ignoring sensor data. Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE. | 637 | | locked | Screen is locked to its current rotation, whatever that is. Corresponds to ActivityInfo.SCREEN_ORIENTATION_LOCKED. | 638 | | nosensor | Always ignore orientation determined by orientation sensor: the display will not rotate when the user moves the device. Corresponds to ActivityInfo.SCREEN_ORIENTATION_NOSENSOR. | 639 | | portrait | Would like to have the screen in a portrait orientation: that is, with the display taller than it is wide, ignoring sensor data. Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT. | 640 | | reverseLandscape | Would like to have the screen in landscape orientation, turned in the opposite direction from normal landscape. Corresponds to ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE. | 641 | | reversePortrait | Would like to have the screen in portrait orientation, turned in the opposite direction from normal portrait. Corresponds to ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT. | 642 | | sensor | Orientation is determined by a physical orientation sensor: the display will rotate based on how the user moves the device. Ignores user's setting to turn off sensor-based rotation. Corresponds to ActivityInfo.SCREEN_ORIENTATION_SENSOR. | 643 | | sensorLandscape | Would like to have the screen in landscape orientation, but can use the sensor to change which direction the screen is facing. Corresponds to ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE. | 644 | | sensorPortrait | Would like to have the screen in portrait orientation, but can use the sensor to change which direction the screen is facing. Corresponds to ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT. | 645 | | unspecified | No preference specified: let the system decide the best orientation. This will either be the orientation selected by the activity below, or the user's preferred orientation if this activity is the bottom of a task. If the user explicitly turned off sensor based orientation through settings sensor based device rotation will be ignored. If not by default sensor based orientation will be taken into account and the orientation will changed based on how the user rotates the device. Corresponds to ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED. | 646 | | user | Use the user's current preferred orientation of the handset. Corresponds to ActivityInfo.SCREEN_ORIENTATION_USER. | 647 | | userLandscape | Would like to have the screen in landscape orientation, but if the user has enabled sensor-based rotation then we can use the sensor to change which direction the screen is facing. Corresponds to ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE. | 648 | | userPortrait | Would like to have the screen in portrait orientation, but if the user has enabled sensor-based rotation then we can use the sensor to change which direction the screen is facing. Corresponds to ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT. | 649 | 650 | 651 | 652 | ## Bugs 653 | Report bugs as GitHub issues: 654 | 655 | 656 | ## Author 657 | Tarek Sander 658 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Termux:GUI Bash 2 | Bash library for Termux:GUI. 3 | 4 | [Documentation](Manual.md) 5 | 6 | [Tutorial](TUTORIAL.md) 7 | 8 | ### Installing 9 | 10 | #### With `pkg` 11 | 12 | Use `pkg install termux-gui-bash` to install. 13 | 14 | #### From Source 15 | 16 | Additional dependency: CMake 17 | 18 | ````bash 19 | git clone https://github.com/tareksander/termux-gui-bash.git 20 | cd termux-gui-bash 21 | cmake . -DCMAKE_INSTALL_PREFIX=$PREFIX 22 | make install 23 | ```` 24 | 25 | ### Dependencies 26 | 27 | Only need to be installed manually when building from source. 28 | 29 | - [jq](https://github.com/stedolan/jq) 30 | - Bash (should be installed by default in Termux) 31 | 32 | ### License 33 | 34 | The license is the [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/). 35 | TL;DR: You can use this library in your own projects, regardless of the license you choose for it. Modifications to this 36 | library have to be published under the MPL 2.0 (or a GNU license in some cases) though. 37 | 38 | -------------------------------------------------------------------------------- /TUTORIAL.md: -------------------------------------------------------------------------------- 1 | # Bash Library Tutorial 2 | 3 | Make sure you installed the library like explained in the README. 4 | This tutorial assumes you have the basic understanding of the Android GUI from 5 | the [crash course](https://github.com/termux/termux-gui). 6 | The full source code can also be found in the tutorial folder. 7 | 8 | ## Loading the library 9 | 10 | To use the library, you have 2 options: 11 | - Use the shebang `#!/bin/tgui-bash` instead of `#!/bin/bash`. The library will initialize itself and then load your script. 12 | - Exec tgui-bash with your script path as argument when tgui-bash is not loaded: `! [ -v tgc_activity_tid ] && exec tgui-bash "${BASH_SOURCE[0]}" "$@"`. 13 | 14 | The library exits when your script exits, and all remaining Activities are cleaned up by the Plugin after that. 15 | 16 | 17 | ## Hello World 18 | 19 | ````bash 20 | #!/bin/tgui-bash 21 | 22 | set -u 23 | 24 | # Parameters for the Activity, can be empty 25 | declare -A aparams=() 26 | # Array to store the Activity id and Task id in 27 | declare -a activity=() 28 | 29 | # Start the Activity 30 | tg_activity_new aparams activity 31 | 32 | # Get the Activity id from the array as a shortcut 33 | aid="${activity[0]}" 34 | 35 | # Parameters for creating a TextView, in this case the initial text. 36 | # For the keys there are constants, to help with IDE autocompletion and 37 | # to make it obvious when you mistyped. 38 | declare -A tparams=([${tgc_create_text}]="Hello World!") 39 | 40 | # Create the TextView 41 | tg_create_text "$aid" tparams 42 | 43 | # Wait 2 seconds before exiting 44 | sleep 2 45 | ```` 46 | 47 | [hello_world.sh](tutorial/hello_world.sh) 48 | 49 | 50 | ## Events 51 | 52 | 53 | In a GUI, you want to react to events caused by the user. 54 | In this library you have the option to poll for events (not recommended) with `tg_msg_recv_event` 55 | and to wait for events with `tg_msg_recv_event_blocking`. 56 | To process events, you have some functions and `jq` available to get the information you need. 57 | Though for most of the events you only need to know that the `.` operator in jq accesses a value of an object. 58 | With that, you can parse the events you need according to the [protocol definition](https://github.com/termux/termux-gui/blob/main/Protocol.md#events). 59 | 60 | 61 | 62 | ````bash 63 | #!/bin/tgui-bash 64 | 65 | set -u 66 | 67 | declare -A aparams=() 68 | declare -a activity=() 69 | 70 | tg_activity_new aparams activity 71 | 72 | aid="${activity[0]}" 73 | 74 | # Parameters for creating a Button, in this case the text. 75 | declare -A bparams=([${tgc_create_text}]="Hello World!") 76 | 77 | # Create the Button and save the id 78 | b="$(tg_create_button "$aid" bparams)" 79 | 80 | while true; do 81 | ev="$(tg_msg_recv_event_blocking)" 82 | 83 | # Print when the button is pressed 84 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_id "$ev")" = "$b" ]; then 85 | echo "Button pressed!" 86 | fi 87 | 88 | # Exit when the Activity is closed 89 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_destroy" ]; then 90 | exit 0 91 | fi 92 | done 93 | ```` 94 | 95 | [events.sh](tutorial/events.sh) 96 | 97 | ## Layout hierarchy 98 | 99 | To arrange Views you need to use Layouts. The simplest one is LinearLayout. 100 | 101 | ````bash 102 | #!/bin/tgui-bash 103 | 104 | set -u 105 | 106 | declare -A aparams=() 107 | declare -a activity=() 108 | 109 | tg_activity_new aparams activity 110 | 111 | aid="${activity[0]}" 112 | 113 | 114 | declare -A lparams=() 115 | 116 | # Create the LinearLayout and save the id 117 | layout="$(tg_create_linear "$aid" lparams)" 118 | 119 | # Create Buttons in the Layout 120 | declare -A bparams=() 121 | for i in {1..5}; do 122 | bparams[$tgc_create_text]=$i 123 | tg_create_button "$aid" bparams "$layout" >/dev/null 124 | done 125 | 126 | while true; do 127 | ev="$(tg_msg_recv_event_blocking)" 128 | # Exit when the Activity is closed 129 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_destroy" ]; then 130 | exit 0 131 | fi 132 | done 133 | ```` 134 | 135 | [linearlayout.sh](tutorial/linearlayout.sh) 136 | 137 | ## Images and picture-in-picture 138 | 139 | You can display images in PNG or JPEG format by base43-encoding them. 140 | The `base64` command is preinstalled in Termux. 141 | To generate the image string you should use `img="$(base64 -w 0 )"`. 142 | 143 | Picture-in-picture mode allows you to show the Activity in a small overlay window. 144 | 145 | 146 | ````bash 147 | #!/bin/tgui-bash 148 | 149 | set -u 150 | 151 | # Base64-encoded image of the Termux banner 152 | banner="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" 153 | 154 | # Let the Activity start in picture-in-picture mode 155 | declare -A aparams=([$tgc_activity_pip]="true") 156 | declare -a activity=() 157 | 158 | tg_activity_new aparams activity 159 | 160 | aid="${activity[0]}" 161 | 162 | # set the aspect ratio to that of the image 163 | tg_activity_pip_params "$aid" "320" "180" 164 | 165 | declare -A imgparams=() 166 | 167 | # Create an ImageView and save the id 168 | img="$(tg_create_image "$aid" imgparams)" 169 | 170 | # Set the image 171 | tg_view_image "$aid" "$img" "$banner" 172 | 173 | while true; do 174 | ev="$(tg_msg_recv_event_blocking)" 175 | # Exit when the user leaves 176 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_stop" ]; then 177 | exit 0 178 | fi 179 | done 180 | ```` 181 | 182 | [image.sh](tutorial/image.sh) 183 | 184 | ## Dialogs & Inputs 185 | 186 | An Activity can also be shown as a dialog. 187 | This make for a great user experience with Termux, as exiting the dialog drops you right back into the terminal without animation. 188 | 189 | 190 | ````bash 191 | #!/bin/tgui-bash 192 | 193 | set -u 194 | 195 | 196 | # Let the Activity start as a dialog 197 | declare -A aparams=([$tgc_activity_dialog]="true") 198 | declare -a activity=() 199 | 200 | tg_activity_new aparams activity 201 | 202 | aid="${activity[0]}" 203 | 204 | declare -A params=() 205 | 206 | layout="$(tg_create_linear "$aid" params)" 207 | 208 | # EditText 209 | et="$(tg_create_edit "$aid" params "$layout")" 210 | 211 | params[$tgc_create_text]="Click me!" 212 | echo "${params[@]}" 213 | # Button 214 | bt="$(tg_create_button "$aid" params "$layout")" 215 | 216 | #unset "params[$tgc_create_text]" 217 | declare -a list=("Option 1" "Option 2" "Option 3" "Option 4") 218 | 219 | # Spinner 220 | sp="$(tg_create_spinner "$aid" params "$layout")" 221 | # Set the options 222 | tg_view_list "$aid" "$sp" list 223 | 224 | # Toggle 225 | tg="$(tg_create_toggle "$aid" params "$layout")" 226 | 227 | params[$tgc_create_text]="Switch" 228 | 229 | # Switch 230 | sw="$(tg_create_switch "$aid" params "$layout")" 231 | 232 | 233 | params[$tgc_create_text]="Checkbox" 234 | # CheckBox 235 | cb="$(tg_create_checkbox "$aid" params "$layout")" 236 | 237 | # Group for RadioButtons 238 | rg="$(tg_create_radio_group "$aid" params "$layout")" 239 | 240 | # Create some RadioButtons 241 | # RadioButtons have to be in a RadioGroup to work 242 | params[$tgc_create_text]="RadioButton 1" 243 | rb1="$(tg_create_radio "$aid" params "$rg")" 244 | 245 | params[$tgc_create_text]="RadioButton 2" 246 | rb2="$(tg_create_radio "$aid" params "$rg")" 247 | 248 | params[$tgc_create_text]="RadioButton 3" 249 | rb3="$(tg_create_radio "$aid" params "$rg")" 250 | 251 | 252 | while true; do 253 | ev="$(tg_msg_recv_event_blocking)" 254 | # Exit when the user leaves 255 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_stop" ]; then 256 | exit 0 257 | fi 258 | # Print out the EditText text when the Button gets clicked 259 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_aid "$ev")" = "$aid" ] && [ "$(tg_event_id "$ev")" = "$bt" ]; then 260 | echo "EditText text: '$(tg_view_get_text "$aid" "$et")'" 261 | fi 262 | # Print the RadioButton id that is selected 263 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_selected" ] && [ "$(tg_event_aid "$ev")" = "$aid" ] && [ "$(tg_event_id "$ev")" = "$rg" ]; then 264 | echo "RadioButton pressed: $(echo "$ev" | jq -r '.value.selected')" 265 | fi 266 | # Print the checked state of other button on click 267 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_aid "$ev")" = "$aid" ] && [ "$(tg_event_id "$ev")" = "$cb" ]; then 268 | echo "CheckBox pressed: $(echo "$ev" | jq -r '.value.set')" 269 | fi 270 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_aid "$ev")" = "$aid" ] && [ "$(tg_event_id "$ev")" = "$sw" ]; then 271 | echo "Switch pressed: $(echo "$ev" | jq -r '.value.set')" 272 | fi 273 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_aid "$ev")" = "$aid" ] && [ "$(tg_event_id "$ev")" = "$tg" ]; then 274 | echo "ToggleButton pressed: $(echo "$ev" | jq -r '.value.set')" 275 | fi 276 | # Print the selected Spinner option 277 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_item_selected" ] && [ "$(tg_event_aid "$ev")" = "$aid" ] && [ "$(tg_event_id "$ev")" = "$sp" ]; then 278 | echo "Spinner option: '$(echo "$ev" | jq -r '.value.selected')'" 279 | fi 280 | done 281 | ```` 282 | 283 | [dialog.sh](tutorial/dialog.sh) 284 | 285 | 286 | 287 | ## Nested Layouts & Scrolling 288 | 289 | By default, LinearLayouts arrange their children vertically. 290 | To create e.g. a bar at the top, you can override this with `$tgc_create_vertical`. 291 | To create a bar however, you should also set the height of the nested layout to `$tgc_view_wrap_content`, 292 | and the layout weight to 0, so it only takes up the space of its children. 293 | Nested layouts can also be used for content in NestedScrollViews, when you can't be sure if the content will fit on the screen. 294 | 295 | 296 | ````bash 297 | #!/bin/tgui-bash 298 | 299 | set -u 300 | 301 | 302 | # Let the Activity start as a dialog 303 | declare -A aparams=() 304 | declare -a activity=() 305 | 306 | tg_activity_new aparams activity 307 | 308 | aid="${activity[0]}" 309 | 310 | declare -A params=() 311 | 312 | layout="$(tg_create_linear "$aid" params)" 313 | 314 | 315 | # Create a Horizontal LinearLayout 316 | params[$tgc_create_vertical]=false 317 | 318 | bar="$(tg_create_linear "$aid" params "$layout")" 319 | 320 | unset "params[$tgc_create_vertical]" 321 | 322 | # Set the height no the minimum needed 323 | tg_view_height "$aid" "$bar" "$tgc_view_wrap_content" 324 | # Don't let it expand to unused space 325 | tg_view_linear "$aid" "$bar" 0 326 | 327 | # Create 2 Buttons in the bar 328 | params[$tgc_create_text]="Bar button 1" 329 | bt1="$(tg_create_button "$aid" params "$bar")" 330 | 331 | params[$tgc_create_text]="Bar button 2" 332 | bt2="$(tg_create_button "$aid" params "$bar")" 333 | 334 | unset "params[$tgc_create_text]" 335 | 336 | 337 | # Create a NestedScrollView and a LinearLayout in it 338 | sc="$(tg_create_nested_scroll "$aid" params "$layout")" 339 | scl="$(tg_create_linear "$aid" params "$sc")" 340 | 341 | # Create Buttons in the NestedScrollView 342 | for i in {1..30}; do 343 | params[$tgc_create_text]="Button $i" 344 | tg_create_button "$aid" params "$scl" >/dev/null 345 | done 346 | 347 | 348 | while true; do 349 | ev="$(tg_msg_recv_event_blocking)" 350 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_destroy" ]; then 351 | exit 0 352 | fi 353 | done 354 | ```` 355 | 356 | [scroll.sh](tutorial/scroll.sh) 357 | 358 | 359 | ## Widgets 360 | 361 | Termux:GUI allows you to create widgets which programs can fill. 362 | The methods for widgets are slightly different, because Android doesn't support as much functionality for widgets. 363 | 364 | 365 | ````bash 366 | #!/bin/tgui-bash 367 | 368 | set -u 369 | 370 | 371 | # Let the Activity start as a dialog 372 | declare -A aparams=() 373 | declare -a activity=() 374 | 375 | tg_activity_new aparams activity 376 | 377 | aid="${activity[0]}" 378 | 379 | declare -A params=() 380 | 381 | layout="$(tg_create_linear "$aid" params)" 382 | 383 | 384 | widfield="$(tg_create_edit "$aid" params "$layout")" 385 | 386 | textfield="$(tg_create_edit "$aid" params "$layout")" 387 | 388 | 389 | params[$tgc_create_text]="Set widget text" 390 | 391 | b="$(tg_create_button "$aid" params "$layout")" 392 | 393 | 394 | while true; do 395 | ev="$(tg_msg_recv_event_blocking)" 396 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_destroy" ]; then 397 | exit 0 398 | fi 399 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_id "$ev")" = "$b" ]; then 400 | # Get the widget id 401 | wid="$(tg_view_get_text "$aid" "$widfield")" 402 | text="$(tg_view_get_text "$aid" "$textfield")" 403 | # Create a remote layout and TextView 404 | rl="$(tg_remote_create_layout)" 405 | rt="$(tg_remote_create_text "$rl")" 406 | # Set the text 407 | tg_remote_text "$rl" "$rt" "$text" 408 | # Set the widget layout and destroy the remote layout again 409 | tg_widget_layout "$rl" "$wid" 410 | tc_remote_delete_layout "$rl" 411 | fi 412 | done 413 | ```` 414 | 415 | If you want to see full programs using this, look at [the examples](https://github.com/tareksander/termux-gui-bash/tree/main/examples). 416 | 417 | 418 | -------------------------------------------------------------------------------- /doc_html/manual-dark.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareksander/termux-gui-bash/4555edfc75a8432f6c564facd09e0f8ba0b7a82d/doc_html/manual-dark.html.gz -------------------------------------------------------------------------------- /doc_html/manual-light.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareksander/termux-gui-bash/4555edfc75a8432f6c564facd09e0f8ba0b7a82d/doc_html/manual-light.html.gz -------------------------------------------------------------------------------- /doc_html/tutorial-dark.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareksander/termux-gui-bash/4555edfc75a8432f6c564facd09e0f8ba0b7a82d/doc_html/tutorial-dark.html.gz -------------------------------------------------------------------------------- /doc_html/tutorial-light.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tareksander/termux-gui-bash/4555edfc75a8432f6c564facd09e0f8ba0b7a82d/doc_html/tutorial-light.html.gz -------------------------------------------------------------------------------- /doc_templates/doctemplate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | $for(author-meta)$ 8 | 9 | $endfor$ 10 | $if(date-meta)$ 11 | 12 | $endif$ 13 | $if(keywords)$ 14 | 15 | $endif$ 16 | $if(title-prefix)$$title-prefix$ – $endif$$pagetitle$ 17 | 20 | $for(css)$ 21 | 22 | $endfor$ 23 | $if(math)$ 24 | $math$ 25 | $endif$ 26 | $for(header-includes)$ 27 | $header-includes$ 28 | $endfor$ 29 | 30 | 31 | $for(include-before)$ 32 | $include-before$ 33 | $endfor$ 34 | $if(title)$ 35 |
36 |

$title$

37 | $if(subtitle)$ 38 |

$subtitle$

39 | $endif$ 40 | $for(author)$ 41 |

$author$

42 | $endfor$ 43 | $if(date)$ 44 |

$date$

45 | $endif$ 46 |
47 | $endif$ 48 | $if(toc)$ 49 | 55 | $endif$ 56 | $body$ 57 | $for(include-after)$ 58 | $include-after$ 59 | $endfor$ 60 | 61 | 62 | -------------------------------------------------------------------------------- /doc_templates/github-markdown-dark.css: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | 12 | */ 13 | 14 | .markdown-body { 15 | color-scheme: dark; 16 | -ms-text-size-adjust: 100%; 17 | -webkit-text-size-adjust: 100%; 18 | margin: 0; 19 | color: #c9d1d9; 20 | background-color: #0d1117; 21 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; 22 | font-size: 16px; 23 | line-height: 1.5; 24 | word-wrap: break-word; 25 | } 26 | 27 | .markdown-body .octicon { 28 | display: inline-block; 29 | fill: currentColor; 30 | vertical-align: text-bottom; 31 | } 32 | 33 | .markdown-body h1:hover .anchor .octicon-link:before, 34 | .markdown-body h2:hover .anchor .octicon-link:before, 35 | .markdown-body h3:hover .anchor .octicon-link:before, 36 | .markdown-body h4:hover .anchor .octicon-link:before, 37 | .markdown-body h5:hover .anchor .octicon-link:before, 38 | .markdown-body h6:hover .anchor .octicon-link:before { 39 | width: 16px; 40 | height: 16px; 41 | content: ' '; 42 | display: inline-block; 43 | background-color: currentColor; 44 | -webkit-mask-image: url("data:image/svg+xml,"); 45 | mask-image: url("data:image/svg+xml,"); 46 | } 47 | 48 | .markdown-body details, 49 | .markdown-body figcaption, 50 | .markdown-body figure { 51 | display: block; 52 | } 53 | 54 | .markdown-body summary { 55 | display: list-item; 56 | } 57 | 58 | .markdown-body [hidden] { 59 | display: none !important; 60 | } 61 | 62 | .markdown-body a { 63 | background-color: transparent; 64 | color: #58a6ff; 65 | text-decoration: none; 66 | } 67 | 68 | .markdown-body a:active, 69 | .markdown-body a:hover { 70 | outline-width: 0; 71 | } 72 | 73 | .markdown-body abbr[title] { 74 | border-bottom: none; 75 | text-decoration: underline dotted; 76 | } 77 | 78 | .markdown-body b, 79 | .markdown-body strong { 80 | font-weight: 600; 81 | } 82 | 83 | .markdown-body dfn { 84 | font-style: italic; 85 | } 86 | 87 | .markdown-body h1 { 88 | margin: .67em 0; 89 | font-weight: 600; 90 | padding-bottom: .3em; 91 | font-size: 2em; 92 | border-bottom: 1px solid #21262d; 93 | } 94 | 95 | .markdown-body mark { 96 | background-color: rgba(187,128,9,0.15); 97 | color: #c9d1d9; 98 | } 99 | 100 | .markdown-body small { 101 | font-size: 90%; 102 | } 103 | 104 | .markdown-body sub, 105 | .markdown-body sup { 106 | font-size: 75%; 107 | line-height: 0; 108 | position: relative; 109 | vertical-align: baseline; 110 | } 111 | 112 | .markdown-body sub { 113 | bottom: -0.25em; 114 | } 115 | 116 | .markdown-body sup { 117 | top: -0.5em; 118 | } 119 | 120 | .markdown-body img { 121 | border-style: none; 122 | max-width: 100%; 123 | box-sizing: content-box; 124 | background-color: #0d1117; 125 | } 126 | 127 | .markdown-body code, 128 | .markdown-body kbd, 129 | .markdown-body pre, 130 | .markdown-body samp { 131 | font-family: monospace,monospace; 132 | font-size: 1em; 133 | } 134 | 135 | .markdown-body figure { 136 | margin: 1em 40px; 137 | } 138 | 139 | .markdown-body hr { 140 | box-sizing: content-box; 141 | overflow: hidden; 142 | background: transparent; 143 | border-bottom: 1px solid #21262d; 144 | height: .25em; 145 | padding: 0; 146 | margin: 24px 0; 147 | background-color: #30363d; 148 | border: 0; 149 | } 150 | 151 | .markdown-body input { 152 | font: inherit; 153 | margin: 0; 154 | overflow: visible; 155 | font-family: inherit; 156 | font-size: inherit; 157 | line-height: inherit; 158 | } 159 | 160 | .markdown-body [type=button], 161 | .markdown-body [type=reset], 162 | .markdown-body [type=submit] { 163 | -webkit-appearance: button; 164 | } 165 | 166 | .markdown-body [type=button]::-moz-focus-inner, 167 | .markdown-body [type=reset]::-moz-focus-inner, 168 | .markdown-body [type=submit]::-moz-focus-inner { 169 | border-style: none; 170 | padding: 0; 171 | } 172 | 173 | .markdown-body [type=button]:-moz-focusring, 174 | .markdown-body [type=reset]:-moz-focusring, 175 | .markdown-body [type=submit]:-moz-focusring { 176 | outline: 1px dotted ButtonText; 177 | } 178 | 179 | .markdown-body [type=checkbox], 180 | .markdown-body [type=radio] { 181 | box-sizing: border-box; 182 | padding: 0; 183 | } 184 | 185 | .markdown-body [type=number]::-webkit-inner-spin-button, 186 | .markdown-body [type=number]::-webkit-outer-spin-button { 187 | height: auto; 188 | } 189 | 190 | .markdown-body [type=search] { 191 | -webkit-appearance: textfield; 192 | outline-offset: -2px; 193 | } 194 | 195 | .markdown-body [type=search]::-webkit-search-cancel-button, 196 | .markdown-body [type=search]::-webkit-search-decoration { 197 | -webkit-appearance: none; 198 | } 199 | 200 | .markdown-body ::-webkit-input-placeholder { 201 | color: inherit; 202 | opacity: .54; 203 | } 204 | 205 | .markdown-body ::-webkit-file-upload-button { 206 | -webkit-appearance: button; 207 | font: inherit; 208 | } 209 | 210 | .markdown-body a:hover { 211 | text-decoration: underline; 212 | } 213 | 214 | .markdown-body hr::before { 215 | display: table; 216 | content: ""; 217 | } 218 | 219 | .markdown-body hr::after { 220 | display: table; 221 | clear: both; 222 | content: ""; 223 | } 224 | 225 | .markdown-body table { 226 | border-spacing: 0; 227 | border-collapse: collapse; 228 | display: block; 229 | width: max-content; 230 | max-width: 100%; 231 | overflow: auto; 232 | } 233 | 234 | .markdown-body td, 235 | .markdown-body th { 236 | padding: 0; 237 | } 238 | 239 | .markdown-body details summary { 240 | cursor: pointer; 241 | } 242 | 243 | .markdown-body details:not([open])>*:not(summary) { 244 | display: none !important; 245 | } 246 | 247 | .markdown-body kbd { 248 | display: inline-block; 249 | padding: 3px 5px; 250 | font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 251 | line-height: 10px; 252 | color: #c9d1d9; 253 | vertical-align: middle; 254 | background-color: #161b22; 255 | border: solid 1px rgba(110,118,129,0.4); 256 | border-bottom-color: rgba(110,118,129,0.4); 257 | border-radius: 6px; 258 | box-shadow: inset 0 -1px 0 rgba(110,118,129,0.4); 259 | } 260 | 261 | .markdown-body h1, 262 | .markdown-body h2, 263 | .markdown-body h3, 264 | .markdown-body h4, 265 | .markdown-body h5, 266 | .markdown-body h6 { 267 | margin-top: 24px; 268 | margin-bottom: 16px; 269 | font-weight: 600; 270 | line-height: 1.25; 271 | } 272 | 273 | .markdown-body h2 { 274 | font-weight: 600; 275 | padding-bottom: .3em; 276 | font-size: 1.5em; 277 | border-bottom: 1px solid #21262d; 278 | } 279 | 280 | .markdown-body h3 { 281 | font-weight: 600; 282 | font-size: 1.25em; 283 | } 284 | 285 | .markdown-body h4 { 286 | font-weight: 600; 287 | font-size: 1em; 288 | } 289 | 290 | .markdown-body h5 { 291 | font-weight: 600; 292 | font-size: .875em; 293 | } 294 | 295 | .markdown-body h6 { 296 | font-weight: 600; 297 | font-size: .85em; 298 | color: #8b949e; 299 | } 300 | 301 | .markdown-body p { 302 | margin-top: 0; 303 | margin-bottom: 10px; 304 | } 305 | 306 | .markdown-body blockquote { 307 | margin: 0; 308 | padding: 0 1em; 309 | color: #8b949e; 310 | border-left: .25em solid #30363d; 311 | } 312 | 313 | .markdown-body ul, 314 | .markdown-body ol { 315 | margin-top: 0; 316 | margin-bottom: 0; 317 | padding-left: 2em; 318 | } 319 | 320 | .markdown-body ol ol, 321 | .markdown-body ul ol { 322 | list-style-type: lower-roman; 323 | } 324 | 325 | .markdown-body ul ul ol, 326 | .markdown-body ul ol ol, 327 | .markdown-body ol ul ol, 328 | .markdown-body ol ol ol { 329 | list-style-type: lower-alpha; 330 | } 331 | 332 | .markdown-body dd { 333 | margin-left: 0; 334 | } 335 | 336 | .markdown-body tt, 337 | .markdown-body code { 338 | font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 339 | font-size: 12px; 340 | } 341 | 342 | .markdown-body pre { 343 | margin-top: 0; 344 | margin-bottom: 0; 345 | font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 346 | font-size: 12px; 347 | word-wrap: normal; 348 | } 349 | 350 | .markdown-body .octicon { 351 | display: inline-block; 352 | overflow: visible !important; 353 | vertical-align: text-bottom; 354 | fill: currentColor; 355 | } 356 | 357 | .markdown-body ::placeholder { 358 | color: #484f58; 359 | opacity: 1; 360 | } 361 | 362 | .markdown-body input::-webkit-outer-spin-button, 363 | .markdown-body input::-webkit-inner-spin-button { 364 | margin: 0; 365 | -webkit-appearance: none; 366 | appearance: none; 367 | } 368 | 369 | .markdown-body .pl-c { 370 | color: #8b949e; 371 | } 372 | 373 | .markdown-body .pl-c1, 374 | .markdown-body .pl-s .pl-v { 375 | color: #79c0ff; 376 | } 377 | 378 | .markdown-body .pl-e, 379 | .markdown-body .pl-en { 380 | color: #d2a8ff; 381 | } 382 | 383 | .markdown-body .pl-smi, 384 | .markdown-body .pl-s .pl-s1 { 385 | color: #c9d1d9; 386 | } 387 | 388 | .markdown-body .pl-ent { 389 | color: #7ee787; 390 | } 391 | 392 | .markdown-body .pl-k { 393 | color: #ff7b72; 394 | } 395 | 396 | .markdown-body .pl-s, 397 | .markdown-body .pl-pds, 398 | .markdown-body .pl-s .pl-pse .pl-s1, 399 | .markdown-body .pl-sr, 400 | .markdown-body .pl-sr .pl-cce, 401 | .markdown-body .pl-sr .pl-sre, 402 | .markdown-body .pl-sr .pl-sra { 403 | color: #a5d6ff; 404 | } 405 | 406 | .markdown-body .pl-v, 407 | .markdown-body .pl-smw { 408 | color: #ffa657; 409 | } 410 | 411 | .markdown-body .pl-bu { 412 | color: #f85149; 413 | } 414 | 415 | .markdown-body .pl-ii { 416 | color: #f0f6fc; 417 | background-color: #8e1519; 418 | } 419 | 420 | .markdown-body .pl-c2 { 421 | color: #f0f6fc; 422 | background-color: #b62324; 423 | } 424 | 425 | .markdown-body .pl-sr .pl-cce { 426 | font-weight: bold; 427 | color: #7ee787; 428 | } 429 | 430 | .markdown-body .pl-ml { 431 | color: #f2cc60; 432 | } 433 | 434 | .markdown-body .pl-mh, 435 | .markdown-body .pl-mh .pl-en, 436 | .markdown-body .pl-ms { 437 | font-weight: bold; 438 | color: #1f6feb; 439 | } 440 | 441 | .markdown-body .pl-mi { 442 | font-style: italic; 443 | color: #c9d1d9; 444 | } 445 | 446 | .markdown-body .pl-mb { 447 | font-weight: bold; 448 | color: #c9d1d9; 449 | } 450 | 451 | .markdown-body .pl-md { 452 | color: #ffdcd7; 453 | background-color: #67060c; 454 | } 455 | 456 | .markdown-body .pl-mi1 { 457 | color: #aff5b4; 458 | background-color: #033a16; 459 | } 460 | 461 | .markdown-body .pl-mc { 462 | color: #ffdfb6; 463 | background-color: #5a1e02; 464 | } 465 | 466 | .markdown-body .pl-mi2 { 467 | color: #c9d1d9; 468 | background-color: #1158c7; 469 | } 470 | 471 | .markdown-body .pl-mdr { 472 | font-weight: bold; 473 | color: #d2a8ff; 474 | } 475 | 476 | .markdown-body .pl-ba { 477 | color: #8b949e; 478 | } 479 | 480 | .markdown-body .pl-sg { 481 | color: #484f58; 482 | } 483 | 484 | .markdown-body .pl-corl { 485 | text-decoration: underline; 486 | color: #a5d6ff; 487 | } 488 | 489 | .markdown-body [data-catalyst] { 490 | display: block; 491 | } 492 | 493 | .markdown-body g-emoji { 494 | font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; 495 | font-size: 1em; 496 | font-style: normal !important; 497 | font-weight: 400; 498 | line-height: 1; 499 | vertical-align: -0.075em; 500 | } 501 | 502 | .markdown-body g-emoji img { 503 | width: 1em; 504 | height: 1em; 505 | } 506 | 507 | .markdown-body::before { 508 | display: table; 509 | content: ""; 510 | } 511 | 512 | .markdown-body::after { 513 | display: table; 514 | clear: both; 515 | content: ""; 516 | } 517 | 518 | .markdown-body>*:first-child { 519 | margin-top: 0 !important; 520 | } 521 | 522 | .markdown-body>*:last-child { 523 | margin-bottom: 0 !important; 524 | } 525 | 526 | .markdown-body a:not([href]) { 527 | color: inherit; 528 | text-decoration: none; 529 | } 530 | 531 | .markdown-body .absent { 532 | color: #f85149; 533 | } 534 | 535 | .markdown-body .anchor { 536 | float: left; 537 | padding-right: 4px; 538 | margin-left: -20px; 539 | line-height: 1; 540 | } 541 | 542 | .markdown-body .anchor:focus { 543 | outline: none; 544 | } 545 | 546 | .markdown-body p, 547 | .markdown-body blockquote, 548 | .markdown-body ul, 549 | .markdown-body ol, 550 | .markdown-body dl, 551 | .markdown-body table, 552 | .markdown-body pre, 553 | .markdown-body details { 554 | margin-top: 0; 555 | margin-bottom: 16px; 556 | } 557 | 558 | .markdown-body blockquote>:first-child { 559 | margin-top: 0; 560 | } 561 | 562 | .markdown-body blockquote>:last-child { 563 | margin-bottom: 0; 564 | } 565 | 566 | .markdown-body sup>a::before { 567 | content: "["; 568 | } 569 | 570 | .markdown-body sup>a::after { 571 | content: "]"; 572 | } 573 | 574 | .markdown-body h1 .octicon-link, 575 | .markdown-body h2 .octicon-link, 576 | .markdown-body h3 .octicon-link, 577 | .markdown-body h4 .octicon-link, 578 | .markdown-body h5 .octicon-link, 579 | .markdown-body h6 .octicon-link { 580 | color: #c9d1d9; 581 | vertical-align: middle; 582 | visibility: hidden; 583 | } 584 | 585 | .markdown-body h1:hover .anchor, 586 | .markdown-body h2:hover .anchor, 587 | .markdown-body h3:hover .anchor, 588 | .markdown-body h4:hover .anchor, 589 | .markdown-body h5:hover .anchor, 590 | .markdown-body h6:hover .anchor { 591 | text-decoration: none; 592 | } 593 | 594 | .markdown-body h1:hover .anchor .octicon-link, 595 | .markdown-body h2:hover .anchor .octicon-link, 596 | .markdown-body h3:hover .anchor .octicon-link, 597 | .markdown-body h4:hover .anchor .octicon-link, 598 | .markdown-body h5:hover .anchor .octicon-link, 599 | .markdown-body h6:hover .anchor .octicon-link { 600 | visibility: visible; 601 | } 602 | 603 | .markdown-body h1 tt, 604 | .markdown-body h1 code, 605 | .markdown-body h2 tt, 606 | .markdown-body h2 code, 607 | .markdown-body h3 tt, 608 | .markdown-body h3 code, 609 | .markdown-body h4 tt, 610 | .markdown-body h4 code, 611 | .markdown-body h5 tt, 612 | .markdown-body h5 code, 613 | .markdown-body h6 tt, 614 | .markdown-body h6 code { 615 | padding: 0 .2em; 616 | font-size: inherit; 617 | } 618 | 619 | .markdown-body ul.no-list, 620 | .markdown-body ol.no-list { 621 | padding: 0; 622 | list-style-type: none; 623 | } 624 | 625 | .markdown-body ol[type="1"] { 626 | list-style-type: decimal; 627 | } 628 | 629 | .markdown-body ol[type=a] { 630 | list-style-type: lower-alpha; 631 | } 632 | 633 | .markdown-body ol[type=i] { 634 | list-style-type: lower-roman; 635 | } 636 | 637 | .markdown-body div>ol:not([type]) { 638 | list-style-type: decimal; 639 | } 640 | 641 | .markdown-body ul ul, 642 | .markdown-body ul ol, 643 | .markdown-body ol ol, 644 | .markdown-body ol ul { 645 | margin-top: 0; 646 | margin-bottom: 0; 647 | } 648 | 649 | .markdown-body li>p { 650 | margin-top: 16px; 651 | } 652 | 653 | .markdown-body li+li { 654 | margin-top: .25em; 655 | } 656 | 657 | .markdown-body dl { 658 | padding: 0; 659 | } 660 | 661 | .markdown-body dl dt { 662 | padding: 0; 663 | margin-top: 16px; 664 | font-size: 1em; 665 | font-style: italic; 666 | font-weight: 600; 667 | } 668 | 669 | .markdown-body dl dd { 670 | padding: 0 16px; 671 | margin-bottom: 16px; 672 | } 673 | 674 | .markdown-body table th { 675 | font-weight: 600; 676 | } 677 | 678 | .markdown-body table th, 679 | .markdown-body table td { 680 | padding: 6px 13px; 681 | border: 1px solid #30363d; 682 | } 683 | 684 | .markdown-body table tr { 685 | background-color: #0d1117; 686 | border-top: 1px solid #21262d; 687 | } 688 | 689 | .markdown-body table tr:nth-child(2n) { 690 | background-color: #161b22; 691 | } 692 | 693 | .markdown-body table img { 694 | background-color: transparent; 695 | } 696 | 697 | .markdown-body img[align=right] { 698 | padding-left: 20px; 699 | } 700 | 701 | .markdown-body img[align=left] { 702 | padding-right: 20px; 703 | } 704 | 705 | .markdown-body .emoji { 706 | max-width: none; 707 | vertical-align: text-top; 708 | background-color: transparent; 709 | } 710 | 711 | .markdown-body span.frame { 712 | display: block; 713 | overflow: hidden; 714 | } 715 | 716 | .markdown-body span.frame>span { 717 | display: block; 718 | float: left; 719 | width: auto; 720 | padding: 7px; 721 | margin: 13px 0 0; 722 | overflow: hidden; 723 | border: 1px solid #30363d; 724 | } 725 | 726 | .markdown-body span.frame span img { 727 | display: block; 728 | float: left; 729 | } 730 | 731 | .markdown-body span.frame span span { 732 | display: block; 733 | padding: 5px 0 0; 734 | clear: both; 735 | color: #c9d1d9; 736 | } 737 | 738 | .markdown-body span.align-center { 739 | display: block; 740 | overflow: hidden; 741 | clear: both; 742 | } 743 | 744 | .markdown-body span.align-center>span { 745 | display: block; 746 | margin: 13px auto 0; 747 | overflow: hidden; 748 | text-align: center; 749 | } 750 | 751 | .markdown-body span.align-center span img { 752 | margin: 0 auto; 753 | text-align: center; 754 | } 755 | 756 | .markdown-body span.align-right { 757 | display: block; 758 | overflow: hidden; 759 | clear: both; 760 | } 761 | 762 | .markdown-body span.align-right>span { 763 | display: block; 764 | margin: 13px 0 0; 765 | overflow: hidden; 766 | text-align: right; 767 | } 768 | 769 | .markdown-body span.align-right span img { 770 | margin: 0; 771 | text-align: right; 772 | } 773 | 774 | .markdown-body span.float-left { 775 | display: block; 776 | float: left; 777 | margin-right: 13px; 778 | overflow: hidden; 779 | } 780 | 781 | .markdown-body span.float-left span { 782 | margin: 13px 0 0; 783 | } 784 | 785 | .markdown-body span.float-right { 786 | display: block; 787 | float: right; 788 | margin-left: 13px; 789 | overflow: hidden; 790 | } 791 | 792 | .markdown-body span.float-right>span { 793 | display: block; 794 | margin: 13px auto 0; 795 | overflow: hidden; 796 | text-align: right; 797 | } 798 | 799 | .markdown-body code, 800 | .markdown-body tt { 801 | padding: .2em .4em; 802 | margin: 0; 803 | font-size: 85%; 804 | background-color: rgba(110,118,129,0.4); 805 | border-radius: 6px; 806 | } 807 | 808 | .markdown-body code br, 809 | .markdown-body tt br { 810 | display: none; 811 | } 812 | 813 | .markdown-body del code { 814 | text-decoration: inherit; 815 | } 816 | 817 | .markdown-body pre code { 818 | font-size: 100%; 819 | } 820 | 821 | .markdown-body pre>code { 822 | padding: 0; 823 | margin: 0; 824 | word-break: normal; 825 | white-space: pre; 826 | background: transparent; 827 | border: 0; 828 | } 829 | 830 | .markdown-body .highlight { 831 | margin-bottom: 16px; 832 | } 833 | 834 | .markdown-body .highlight pre { 835 | margin-bottom: 0; 836 | word-break: normal; 837 | } 838 | 839 | .markdown-body .highlight pre, 840 | .markdown-body pre { 841 | padding: 16px; 842 | overflow: auto; 843 | font-size: 85%; 844 | line-height: 1.45; 845 | background-color: #161b22; 846 | border-radius: 6px; 847 | } 848 | 849 | .markdown-body pre code, 850 | .markdown-body pre tt { 851 | display: inline; 852 | max-width: auto; 853 | padding: 0; 854 | margin: 0; 855 | overflow: visible; 856 | line-height: inherit; 857 | word-wrap: normal; 858 | background-color: transparent; 859 | border: 0; 860 | } 861 | 862 | .markdown-body .csv-data td, 863 | .markdown-body .csv-data th { 864 | padding: 5px; 865 | overflow: hidden; 866 | font-size: 12px; 867 | line-height: 1; 868 | text-align: left; 869 | white-space: nowrap; 870 | } 871 | 872 | .markdown-body .csv-data .blob-num { 873 | padding: 10px 8px 9px; 874 | text-align: right; 875 | background: #0d1117; 876 | border: 0; 877 | } 878 | 879 | .markdown-body .csv-data tr { 880 | border-top: 0; 881 | } 882 | 883 | .markdown-body .csv-data th { 884 | font-weight: 600; 885 | background: #161b22; 886 | border-top: 0; 887 | } 888 | 889 | .markdown-body .footnotes { 890 | font-size: 12px; 891 | color: #8b949e; 892 | border-top: 1px solid #30363d; 893 | } 894 | 895 | .markdown-body .footnotes ol { 896 | padding-left: 16px; 897 | } 898 | 899 | .markdown-body .footnotes li { 900 | position: relative; 901 | } 902 | 903 | .markdown-body .footnotes li:target::before { 904 | position: absolute; 905 | top: -8px; 906 | right: -8px; 907 | bottom: -8px; 908 | left: -24px; 909 | pointer-events: none; 910 | content: ""; 911 | border: 2px solid #1f6feb; 912 | border-radius: 6px; 913 | } 914 | 915 | .markdown-body .footnotes li:target { 916 | color: #c9d1d9; 917 | } 918 | 919 | .markdown-body .footnotes .data-footnote-backref g-emoji { 920 | font-family: monospace; 921 | } 922 | 923 | .markdown-body .task-list-item { 924 | list-style-type: none; 925 | } 926 | 927 | .markdown-body .task-list-item label { 928 | font-weight: 400; 929 | } 930 | 931 | .markdown-body .task-list-item.enabled label { 932 | cursor: pointer; 933 | } 934 | 935 | .markdown-body .task-list-item+.task-list-item { 936 | margin-top: 3px; 937 | } 938 | 939 | .markdown-body .task-list-item .handle { 940 | display: none; 941 | } 942 | 943 | .markdown-body .task-list-item-checkbox { 944 | margin: 0 .2em .25em -1.6em; 945 | vertical-align: middle; 946 | } 947 | 948 | .markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox { 949 | margin: 0 -1.6em .25em .2em; 950 | } 951 | 952 | .markdown-body ::-webkit-calendar-picker-indicator { 953 | filter: invert(50%); 954 | } 955 | -------------------------------------------------------------------------------- /doc_templates/github-markdown-light.css: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | 12 | */ 13 | 14 | .markdown-body { 15 | -ms-text-size-adjust: 100%; 16 | -webkit-text-size-adjust: 100%; 17 | margin: 0; 18 | color: #24292f; 19 | background-color: #ffffff; 20 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; 21 | font-size: 16px; 22 | line-height: 1.5; 23 | word-wrap: break-word; 24 | } 25 | 26 | .markdown-body .octicon { 27 | display: inline-block; 28 | fill: currentColor; 29 | vertical-align: text-bottom; 30 | } 31 | 32 | .markdown-body h1:hover .anchor .octicon-link:before, 33 | .markdown-body h2:hover .anchor .octicon-link:before, 34 | .markdown-body h3:hover .anchor .octicon-link:before, 35 | .markdown-body h4:hover .anchor .octicon-link:before, 36 | .markdown-body h5:hover .anchor .octicon-link:before, 37 | .markdown-body h6:hover .anchor .octicon-link:before { 38 | width: 16px; 39 | height: 16px; 40 | content: ' '; 41 | display: inline-block; 42 | background-color: currentColor; 43 | -webkit-mask-image: url("data:image/svg+xml,"); 44 | mask-image: url("data:image/svg+xml,"); 45 | } 46 | 47 | .markdown-body details, 48 | .markdown-body figcaption, 49 | .markdown-body figure { 50 | display: block; 51 | } 52 | 53 | .markdown-body summary { 54 | display: list-item; 55 | } 56 | 57 | .markdown-body [hidden] { 58 | display: none !important; 59 | } 60 | 61 | .markdown-body a { 62 | background-color: transparent; 63 | color: #0969da; 64 | text-decoration: none; 65 | } 66 | 67 | .markdown-body a:active, 68 | .markdown-body a:hover { 69 | outline-width: 0; 70 | } 71 | 72 | .markdown-body abbr[title] { 73 | border-bottom: none; 74 | text-decoration: underline dotted; 75 | } 76 | 77 | .markdown-body b, 78 | .markdown-body strong { 79 | font-weight: 600; 80 | } 81 | 82 | .markdown-body dfn { 83 | font-style: italic; 84 | } 85 | 86 | .markdown-body h1 { 87 | margin: .67em 0; 88 | font-weight: 600; 89 | padding-bottom: .3em; 90 | font-size: 2em; 91 | border-bottom: 1px solid hsla(210,18%,87%,1); 92 | } 93 | 94 | .markdown-body mark { 95 | background-color: #fff8c5; 96 | color: #24292f; 97 | } 98 | 99 | .markdown-body small { 100 | font-size: 90%; 101 | } 102 | 103 | .markdown-body sub, 104 | .markdown-body sup { 105 | font-size: 75%; 106 | line-height: 0; 107 | position: relative; 108 | vertical-align: baseline; 109 | } 110 | 111 | .markdown-body sub { 112 | bottom: -0.25em; 113 | } 114 | 115 | .markdown-body sup { 116 | top: -0.5em; 117 | } 118 | 119 | .markdown-body img { 120 | border-style: none; 121 | max-width: 100%; 122 | box-sizing: content-box; 123 | background-color: #ffffff; 124 | } 125 | 126 | .markdown-body code, 127 | .markdown-body kbd, 128 | .markdown-body pre, 129 | .markdown-body samp { 130 | font-family: monospace,monospace; 131 | font-size: 1em; 132 | } 133 | 134 | .markdown-body figure { 135 | margin: 1em 40px; 136 | } 137 | 138 | .markdown-body hr { 139 | box-sizing: content-box; 140 | overflow: hidden; 141 | background: transparent; 142 | border-bottom: 1px solid hsla(210,18%,87%,1); 143 | height: .25em; 144 | padding: 0; 145 | margin: 24px 0; 146 | background-color: #d0d7de; 147 | border: 0; 148 | } 149 | 150 | .markdown-body input { 151 | font: inherit; 152 | margin: 0; 153 | overflow: visible; 154 | font-family: inherit; 155 | font-size: inherit; 156 | line-height: inherit; 157 | } 158 | 159 | .markdown-body [type=button], 160 | .markdown-body [type=reset], 161 | .markdown-body [type=submit] { 162 | -webkit-appearance: button; 163 | } 164 | 165 | .markdown-body [type=button]::-moz-focus-inner, 166 | .markdown-body [type=reset]::-moz-focus-inner, 167 | .markdown-body [type=submit]::-moz-focus-inner { 168 | border-style: none; 169 | padding: 0; 170 | } 171 | 172 | .markdown-body [type=button]:-moz-focusring, 173 | .markdown-body [type=reset]:-moz-focusring, 174 | .markdown-body [type=submit]:-moz-focusring { 175 | outline: 1px dotted ButtonText; 176 | } 177 | 178 | .markdown-body [type=checkbox], 179 | .markdown-body [type=radio] { 180 | box-sizing: border-box; 181 | padding: 0; 182 | } 183 | 184 | .markdown-body [type=number]::-webkit-inner-spin-button, 185 | .markdown-body [type=number]::-webkit-outer-spin-button { 186 | height: auto; 187 | } 188 | 189 | .markdown-body [type=search] { 190 | -webkit-appearance: textfield; 191 | outline-offset: -2px; 192 | } 193 | 194 | .markdown-body [type=search]::-webkit-search-cancel-button, 195 | .markdown-body [type=search]::-webkit-search-decoration { 196 | -webkit-appearance: none; 197 | } 198 | 199 | .markdown-body ::-webkit-input-placeholder { 200 | color: inherit; 201 | opacity: .54; 202 | } 203 | 204 | .markdown-body ::-webkit-file-upload-button { 205 | -webkit-appearance: button; 206 | font: inherit; 207 | } 208 | 209 | .markdown-body a:hover { 210 | text-decoration: underline; 211 | } 212 | 213 | .markdown-body hr::before { 214 | display: table; 215 | content: ""; 216 | } 217 | 218 | .markdown-body hr::after { 219 | display: table; 220 | clear: both; 221 | content: ""; 222 | } 223 | 224 | .markdown-body table { 225 | border-spacing: 0; 226 | border-collapse: collapse; 227 | display: block; 228 | width: max-content; 229 | max-width: 100%; 230 | overflow: auto; 231 | } 232 | 233 | .markdown-body td, 234 | .markdown-body th { 235 | padding: 0; 236 | } 237 | 238 | .markdown-body details summary { 239 | cursor: pointer; 240 | } 241 | 242 | .markdown-body details:not([open])>*:not(summary) { 243 | display: none !important; 244 | } 245 | 246 | .markdown-body kbd { 247 | display: inline-block; 248 | padding: 3px 5px; 249 | font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 250 | line-height: 10px; 251 | color: #24292f; 252 | vertical-align: middle; 253 | background-color: #f6f8fa; 254 | border: solid 1px rgba(175,184,193,0.2); 255 | border-bottom-color: rgba(175,184,193,0.2); 256 | border-radius: 6px; 257 | box-shadow: inset 0 -1px 0 rgba(175,184,193,0.2); 258 | } 259 | 260 | .markdown-body h1, 261 | .markdown-body h2, 262 | .markdown-body h3, 263 | .markdown-body h4, 264 | .markdown-body h5, 265 | .markdown-body h6 { 266 | margin-top: 24px; 267 | margin-bottom: 16px; 268 | font-weight: 600; 269 | line-height: 1.25; 270 | } 271 | 272 | .markdown-body h2 { 273 | font-weight: 600; 274 | padding-bottom: .3em; 275 | font-size: 1.5em; 276 | border-bottom: 1px solid hsla(210,18%,87%,1); 277 | } 278 | 279 | .markdown-body h3 { 280 | font-weight: 600; 281 | font-size: 1.25em; 282 | } 283 | 284 | .markdown-body h4 { 285 | font-weight: 600; 286 | font-size: 1em; 287 | } 288 | 289 | .markdown-body h5 { 290 | font-weight: 600; 291 | font-size: .875em; 292 | } 293 | 294 | .markdown-body h6 { 295 | font-weight: 600; 296 | font-size: .85em; 297 | color: #57606a; 298 | } 299 | 300 | .markdown-body p { 301 | margin-top: 0; 302 | margin-bottom: 10px; 303 | } 304 | 305 | .markdown-body blockquote { 306 | margin: 0; 307 | padding: 0 1em; 308 | color: #57606a; 309 | border-left: .25em solid #d0d7de; 310 | } 311 | 312 | .markdown-body ul, 313 | .markdown-body ol { 314 | margin-top: 0; 315 | margin-bottom: 0; 316 | padding-left: 2em; 317 | } 318 | 319 | .markdown-body ol ol, 320 | .markdown-body ul ol { 321 | list-style-type: lower-roman; 322 | } 323 | 324 | .markdown-body ul ul ol, 325 | .markdown-body ul ol ol, 326 | .markdown-body ol ul ol, 327 | .markdown-body ol ol ol { 328 | list-style-type: lower-alpha; 329 | } 330 | 331 | .markdown-body dd { 332 | margin-left: 0; 333 | } 334 | 335 | .markdown-body tt, 336 | .markdown-body code { 337 | font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 338 | font-size: 12px; 339 | } 340 | 341 | .markdown-body pre { 342 | margin-top: 0; 343 | margin-bottom: 0; 344 | font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 345 | font-size: 12px; 346 | word-wrap: normal; 347 | } 348 | 349 | .markdown-body .octicon { 350 | display: inline-block; 351 | overflow: visible !important; 352 | vertical-align: text-bottom; 353 | fill: currentColor; 354 | } 355 | 356 | .markdown-body ::placeholder { 357 | color: #6e7781; 358 | opacity: 1; 359 | } 360 | 361 | .markdown-body input::-webkit-outer-spin-button, 362 | .markdown-body input::-webkit-inner-spin-button { 363 | margin: 0; 364 | -webkit-appearance: none; 365 | appearance: none; 366 | } 367 | 368 | .markdown-body .pl-c { 369 | color: #6e7781; 370 | } 371 | 372 | .markdown-body .pl-c1, 373 | .markdown-body .pl-s .pl-v { 374 | color: #0550ae; 375 | } 376 | 377 | .markdown-body .pl-e, 378 | .markdown-body .pl-en { 379 | color: #8250df; 380 | } 381 | 382 | .markdown-body .pl-smi, 383 | .markdown-body .pl-s .pl-s1 { 384 | color: #24292f; 385 | } 386 | 387 | .markdown-body .pl-ent { 388 | color: #116329; 389 | } 390 | 391 | .markdown-body .pl-k { 392 | color: #cf222e; 393 | } 394 | 395 | .markdown-body .pl-s, 396 | .markdown-body .pl-pds, 397 | .markdown-body .pl-s .pl-pse .pl-s1, 398 | .markdown-body .pl-sr, 399 | .markdown-body .pl-sr .pl-cce, 400 | .markdown-body .pl-sr .pl-sre, 401 | .markdown-body .pl-sr .pl-sra { 402 | color: #0a3069; 403 | } 404 | 405 | .markdown-body .pl-v, 406 | .markdown-body .pl-smw { 407 | color: #953800; 408 | } 409 | 410 | .markdown-body .pl-bu { 411 | color: #82071e; 412 | } 413 | 414 | .markdown-body .pl-ii { 415 | color: #f6f8fa; 416 | background-color: #82071e; 417 | } 418 | 419 | .markdown-body .pl-c2 { 420 | color: #f6f8fa; 421 | background-color: #cf222e; 422 | } 423 | 424 | .markdown-body .pl-sr .pl-cce { 425 | font-weight: bold; 426 | color: #116329; 427 | } 428 | 429 | .markdown-body .pl-ml { 430 | color: #3b2300; 431 | } 432 | 433 | .markdown-body .pl-mh, 434 | .markdown-body .pl-mh .pl-en, 435 | .markdown-body .pl-ms { 436 | font-weight: bold; 437 | color: #0550ae; 438 | } 439 | 440 | .markdown-body .pl-mi { 441 | font-style: italic; 442 | color: #24292f; 443 | } 444 | 445 | .markdown-body .pl-mb { 446 | font-weight: bold; 447 | color: #24292f; 448 | } 449 | 450 | .markdown-body .pl-md { 451 | color: #82071e; 452 | background-color: #FFEBE9; 453 | } 454 | 455 | .markdown-body .pl-mi1 { 456 | color: #116329; 457 | background-color: #dafbe1; 458 | } 459 | 460 | .markdown-body .pl-mc { 461 | color: #953800; 462 | background-color: #ffd8b5; 463 | } 464 | 465 | .markdown-body .pl-mi2 { 466 | color: #eaeef2; 467 | background-color: #0550ae; 468 | } 469 | 470 | .markdown-body .pl-mdr { 471 | font-weight: bold; 472 | color: #8250df; 473 | } 474 | 475 | .markdown-body .pl-ba { 476 | color: #57606a; 477 | } 478 | 479 | .markdown-body .pl-sg { 480 | color: #8c959f; 481 | } 482 | 483 | .markdown-body .pl-corl { 484 | text-decoration: underline; 485 | color: #0a3069; 486 | } 487 | 488 | .markdown-body [data-catalyst] { 489 | display: block; 490 | } 491 | 492 | .markdown-body g-emoji { 493 | font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; 494 | font-size: 1em; 495 | font-style: normal !important; 496 | font-weight: 400; 497 | line-height: 1; 498 | vertical-align: -0.075em; 499 | } 500 | 501 | .markdown-body g-emoji img { 502 | width: 1em; 503 | height: 1em; 504 | } 505 | 506 | .markdown-body::before { 507 | display: table; 508 | content: ""; 509 | } 510 | 511 | .markdown-body::after { 512 | display: table; 513 | clear: both; 514 | content: ""; 515 | } 516 | 517 | .markdown-body>*:first-child { 518 | margin-top: 0 !important; 519 | } 520 | 521 | .markdown-body>*:last-child { 522 | margin-bottom: 0 !important; 523 | } 524 | 525 | .markdown-body a:not([href]) { 526 | color: inherit; 527 | text-decoration: none; 528 | } 529 | 530 | .markdown-body .absent { 531 | color: #cf222e; 532 | } 533 | 534 | .markdown-body .anchor { 535 | float: left; 536 | padding-right: 4px; 537 | margin-left: -20px; 538 | line-height: 1; 539 | } 540 | 541 | .markdown-body .anchor:focus { 542 | outline: none; 543 | } 544 | 545 | .markdown-body p, 546 | .markdown-body blockquote, 547 | .markdown-body ul, 548 | .markdown-body ol, 549 | .markdown-body dl, 550 | .markdown-body table, 551 | .markdown-body pre, 552 | .markdown-body details { 553 | margin-top: 0; 554 | margin-bottom: 16px; 555 | } 556 | 557 | .markdown-body blockquote>:first-child { 558 | margin-top: 0; 559 | } 560 | 561 | .markdown-body blockquote>:last-child { 562 | margin-bottom: 0; 563 | } 564 | 565 | .markdown-body sup>a::before { 566 | content: "["; 567 | } 568 | 569 | .markdown-body sup>a::after { 570 | content: "]"; 571 | } 572 | 573 | .markdown-body h1 .octicon-link, 574 | .markdown-body h2 .octicon-link, 575 | .markdown-body h3 .octicon-link, 576 | .markdown-body h4 .octicon-link, 577 | .markdown-body h5 .octicon-link, 578 | .markdown-body h6 .octicon-link { 579 | color: #24292f; 580 | vertical-align: middle; 581 | visibility: hidden; 582 | } 583 | 584 | .markdown-body h1:hover .anchor, 585 | .markdown-body h2:hover .anchor, 586 | .markdown-body h3:hover .anchor, 587 | .markdown-body h4:hover .anchor, 588 | .markdown-body h5:hover .anchor, 589 | .markdown-body h6:hover .anchor { 590 | text-decoration: none; 591 | } 592 | 593 | .markdown-body h1:hover .anchor .octicon-link, 594 | .markdown-body h2:hover .anchor .octicon-link, 595 | .markdown-body h3:hover .anchor .octicon-link, 596 | .markdown-body h4:hover .anchor .octicon-link, 597 | .markdown-body h5:hover .anchor .octicon-link, 598 | .markdown-body h6:hover .anchor .octicon-link { 599 | visibility: visible; 600 | } 601 | 602 | .markdown-body h1 tt, 603 | .markdown-body h1 code, 604 | .markdown-body h2 tt, 605 | .markdown-body h2 code, 606 | .markdown-body h3 tt, 607 | .markdown-body h3 code, 608 | .markdown-body h4 tt, 609 | .markdown-body h4 code, 610 | .markdown-body h5 tt, 611 | .markdown-body h5 code, 612 | .markdown-body h6 tt, 613 | .markdown-body h6 code { 614 | padding: 0 .2em; 615 | font-size: inherit; 616 | } 617 | 618 | .markdown-body ul.no-list, 619 | .markdown-body ol.no-list { 620 | padding: 0; 621 | list-style-type: none; 622 | } 623 | 624 | .markdown-body ol[type="1"] { 625 | list-style-type: decimal; 626 | } 627 | 628 | .markdown-body ol[type=a] { 629 | list-style-type: lower-alpha; 630 | } 631 | 632 | .markdown-body ol[type=i] { 633 | list-style-type: lower-roman; 634 | } 635 | 636 | .markdown-body div>ol:not([type]) { 637 | list-style-type: decimal; 638 | } 639 | 640 | .markdown-body ul ul, 641 | .markdown-body ul ol, 642 | .markdown-body ol ol, 643 | .markdown-body ol ul { 644 | margin-top: 0; 645 | margin-bottom: 0; 646 | } 647 | 648 | .markdown-body li>p { 649 | margin-top: 16px; 650 | } 651 | 652 | .markdown-body li+li { 653 | margin-top: .25em; 654 | } 655 | 656 | .markdown-body dl { 657 | padding: 0; 658 | } 659 | 660 | .markdown-body dl dt { 661 | padding: 0; 662 | margin-top: 16px; 663 | font-size: 1em; 664 | font-style: italic; 665 | font-weight: 600; 666 | } 667 | 668 | .markdown-body dl dd { 669 | padding: 0 16px; 670 | margin-bottom: 16px; 671 | } 672 | 673 | .markdown-body table th { 674 | font-weight: 600; 675 | } 676 | 677 | .markdown-body table th, 678 | .markdown-body table td { 679 | padding: 6px 13px; 680 | border: 1px solid #d0d7de; 681 | } 682 | 683 | .markdown-body table tr { 684 | background-color: #ffffff; 685 | border-top: 1px solid hsla(210,18%,87%,1); 686 | } 687 | 688 | .markdown-body table tr:nth-child(2n) { 689 | background-color: #f6f8fa; 690 | } 691 | 692 | .markdown-body table img { 693 | background-color: transparent; 694 | } 695 | 696 | .markdown-body img[align=right] { 697 | padding-left: 20px; 698 | } 699 | 700 | .markdown-body img[align=left] { 701 | padding-right: 20px; 702 | } 703 | 704 | .markdown-body .emoji { 705 | max-width: none; 706 | vertical-align: text-top; 707 | background-color: transparent; 708 | } 709 | 710 | .markdown-body span.frame { 711 | display: block; 712 | overflow: hidden; 713 | } 714 | 715 | .markdown-body span.frame>span { 716 | display: block; 717 | float: left; 718 | width: auto; 719 | padding: 7px; 720 | margin: 13px 0 0; 721 | overflow: hidden; 722 | border: 1px solid #d0d7de; 723 | } 724 | 725 | .markdown-body span.frame span img { 726 | display: block; 727 | float: left; 728 | } 729 | 730 | .markdown-body span.frame span span { 731 | display: block; 732 | padding: 5px 0 0; 733 | clear: both; 734 | color: #24292f; 735 | } 736 | 737 | .markdown-body span.align-center { 738 | display: block; 739 | overflow: hidden; 740 | clear: both; 741 | } 742 | 743 | .markdown-body span.align-center>span { 744 | display: block; 745 | margin: 13px auto 0; 746 | overflow: hidden; 747 | text-align: center; 748 | } 749 | 750 | .markdown-body span.align-center span img { 751 | margin: 0 auto; 752 | text-align: center; 753 | } 754 | 755 | .markdown-body span.align-right { 756 | display: block; 757 | overflow: hidden; 758 | clear: both; 759 | } 760 | 761 | .markdown-body span.align-right>span { 762 | display: block; 763 | margin: 13px 0 0; 764 | overflow: hidden; 765 | text-align: right; 766 | } 767 | 768 | .markdown-body span.align-right span img { 769 | margin: 0; 770 | text-align: right; 771 | } 772 | 773 | .markdown-body span.float-left { 774 | display: block; 775 | float: left; 776 | margin-right: 13px; 777 | overflow: hidden; 778 | } 779 | 780 | .markdown-body span.float-left span { 781 | margin: 13px 0 0; 782 | } 783 | 784 | .markdown-body span.float-right { 785 | display: block; 786 | float: right; 787 | margin-left: 13px; 788 | overflow: hidden; 789 | } 790 | 791 | .markdown-body span.float-right>span { 792 | display: block; 793 | margin: 13px auto 0; 794 | overflow: hidden; 795 | text-align: right; 796 | } 797 | 798 | .markdown-body code, 799 | .markdown-body tt { 800 | padding: .2em .4em; 801 | margin: 0; 802 | font-size: 85%; 803 | background-color: rgba(175,184,193,0.2); 804 | border-radius: 6px; 805 | } 806 | 807 | .markdown-body code br, 808 | .markdown-body tt br { 809 | display: none; 810 | } 811 | 812 | .markdown-body del code { 813 | text-decoration: inherit; 814 | } 815 | 816 | .markdown-body pre code { 817 | font-size: 100%; 818 | } 819 | 820 | .markdown-body pre>code { 821 | padding: 0; 822 | margin: 0; 823 | word-break: normal; 824 | white-space: pre; 825 | background: transparent; 826 | border: 0; 827 | } 828 | 829 | .markdown-body .highlight { 830 | margin-bottom: 16px; 831 | } 832 | 833 | .markdown-body .highlight pre { 834 | margin-bottom: 0; 835 | word-break: normal; 836 | } 837 | 838 | .markdown-body .highlight pre, 839 | .markdown-body pre { 840 | padding: 16px; 841 | overflow: auto; 842 | font-size: 85%; 843 | line-height: 1.45; 844 | background-color: #f6f8fa; 845 | border-radius: 6px; 846 | } 847 | 848 | .markdown-body pre code, 849 | .markdown-body pre tt { 850 | display: inline; 851 | max-width: auto; 852 | padding: 0; 853 | margin: 0; 854 | overflow: visible; 855 | line-height: inherit; 856 | word-wrap: normal; 857 | background-color: transparent; 858 | border: 0; 859 | } 860 | 861 | .markdown-body .csv-data td, 862 | .markdown-body .csv-data th { 863 | padding: 5px; 864 | overflow: hidden; 865 | font-size: 12px; 866 | line-height: 1; 867 | text-align: left; 868 | white-space: nowrap; 869 | } 870 | 871 | .markdown-body .csv-data .blob-num { 872 | padding: 10px 8px 9px; 873 | text-align: right; 874 | background: #ffffff; 875 | border: 0; 876 | } 877 | 878 | .markdown-body .csv-data tr { 879 | border-top: 0; 880 | } 881 | 882 | .markdown-body .csv-data th { 883 | font-weight: 600; 884 | background: #f6f8fa; 885 | border-top: 0; 886 | } 887 | 888 | .markdown-body .footnotes { 889 | font-size: 12px; 890 | color: #57606a; 891 | border-top: 1px solid #d0d7de; 892 | } 893 | 894 | .markdown-body .footnotes ol { 895 | padding-left: 16px; 896 | } 897 | 898 | .markdown-body .footnotes li { 899 | position: relative; 900 | } 901 | 902 | .markdown-body .footnotes li:target::before { 903 | position: absolute; 904 | top: -8px; 905 | right: -8px; 906 | bottom: -8px; 907 | left: -24px; 908 | pointer-events: none; 909 | content: ""; 910 | border: 2px solid #0969da; 911 | border-radius: 6px; 912 | } 913 | 914 | .markdown-body .footnotes li:target { 915 | color: #24292f; 916 | } 917 | 918 | .markdown-body .footnotes .data-footnote-backref g-emoji { 919 | font-family: monospace; 920 | } 921 | 922 | .markdown-body .task-list-item { 923 | list-style-type: none; 924 | } 925 | 926 | .markdown-body .task-list-item label { 927 | font-weight: 400; 928 | } 929 | 930 | .markdown-body .task-list-item.enabled label { 931 | cursor: pointer; 932 | } 933 | 934 | .markdown-body .task-list-item+.task-list-item { 935 | margin-top: 3px; 936 | } 937 | 938 | .markdown-body .task-list-item .handle { 939 | display: none; 940 | } 941 | 942 | .markdown-body .task-list-item-checkbox { 943 | margin: 0 .2em .25em -1.6em; 944 | vertical-align: middle; 945 | } 946 | 947 | .markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox { 948 | margin: 0 -1.6em .25em .2em; 949 | } 950 | 951 | .markdown-body ::-webkit-calendar-picker-indicator { 952 | filter: invert(50%); 953 | } 954 | -------------------------------------------------------------------------------- /examples/yt-dl-gui: -------------------------------------------------------------------------------- 1 | #!/bin/tgui-bash 2 | 3 | set -uo pipefail 4 | 5 | # Parse options 6 | opts="$(exec -a "yt-dl-gui" getopt -s bash -n "yt-dl-gui" -o "dht" -l "dialog,help,no-thumbnail" -- "$@")" 7 | if [ $? -ne 0 ]; then 8 | exit 1 9 | fi 10 | eval set -- "$opts" 11 | unset opts 12 | 13 | dialog=false 14 | thumbnail=true 15 | 16 | 17 | function print_help() { 18 | cat <<"EOF" 19 | Usage: 20 | yt-dl-gui [options] url 21 | 22 | Download a Youtube video with youtube-dl. 23 | 24 | Options: 25 | -h --help Print this help. 26 | -d --dialog Use a dialog instead of a fullscreen window. 27 | -n --no-thumbnail Don't load and display the thumbnail. 28 | EOF 29 | exit 1 30 | } 31 | 32 | 33 | while [ $# -gt 0 ]; do 34 | case "$1" in 35 | -d|--dialog) dialog=true; shift;; 36 | -h|--help) print_help;; 37 | -t|--no-thumbnail) thumbnail=""; shift;; 38 | --) shift; break;; 39 | esac 40 | done 41 | 42 | if [ $# -ne 1 ]; then 43 | echo "yt-dl-gui: One video url needed." >&2 44 | exit 1 45 | fi 46 | 47 | # Check for youtube-dl 48 | if ! command -v youtube-dl &>/dev/null; then 49 | read -r -p "youtube-dl not found. Install? (y/N) " 50 | if [ "$REPLY" = "y" ]; then 51 | pip install youtube-dl 52 | else 53 | exit 1 54 | fi 55 | fi 56 | 57 | # Check for ffmpeg 58 | if ! command -v youtube-dl &>/dev/null; then 59 | read -r -p "ffmpeg not found. Install? (y/N) " 60 | if [ "$REPLY" = "y" ]; then 61 | pkg install ffmpeg 62 | else 63 | exit 1 64 | fi 65 | fi 66 | 67 | # Get video info 68 | info_json="$(youtube-dl -j "$1")" 69 | if [ $? -ne 0 ]; then 70 | echo "youtube-dl error" >&2 71 | exit 1 72 | fi 73 | 74 | thumbnail_url="$(echo "$info_json" | jq -r '.thumbnail')" 75 | 76 | 77 | 78 | 79 | # Create the UI 80 | 81 | declare -A aparams=([$tgc_activity_dialog]="$dialog") 82 | declare -a activity 83 | 84 | tg_activity_new aparams activity 85 | 86 | 87 | aid="${activity[0]}" 88 | 89 | declare -A params=() 90 | 91 | layout="$(tg_create_linear "$aid" params)" 92 | tg_view_margin "$aid" "$layout" 10 93 | 94 | if [ "$thumbnail" ]; then 95 | thumbnail="$(curl -s "$thumbnail_url" | base64 -w 0)" 96 | ti="$(tg_create_image "$aid" params "$layout")" 97 | tg_view_image "$aid" "$ti" "$thumbnail" 98 | tg_view_height "$aid" "$ti" "$tgc_view_wrap_content" 99 | tg_view_linear "$aid" "$ti" 0 100 | fi 101 | 102 | params[$tgc_create_text]="Filename:" 103 | filenametext="$(tg_create_text "$aid" params "$layout")" 104 | tg_view_height "$aid" "$filenametext" "$tgc_view_wrap_content" 105 | tg_view_linear "$aid" "$filenametext" 0 106 | unset "params[$tgc_create_text]" 107 | 108 | 109 | params[$tgc_create_single_line]=true 110 | filenameedit="$(tg_create_edit "$aid" params "$layout")" 111 | tg_view_height "$aid" "$filenameedit" "$tgc_view_wrap_content" 112 | tg_view_linear "$aid" "$filenameedit" 0 113 | unset "params[$tgc_create_single_line]" 114 | 115 | tg_view_text "$aid" "$filenameedit" "$(echo "$info_json" | jq -r '.title').mp4" 116 | 117 | aformats="$(echo "$info_json" | jq '.formats[] | select(.vcodec == "none")')" 118 | vformats="$(echo "$info_json" | jq '.formats[] | select(.acodec == "none")')" 119 | 120 | readarray aformatsarray < <(echo "$aformats" | jq -r '.format') 121 | readarray vformatsarray < <(echo "$vformats" | jq -r '.format') 122 | 123 | params[$tgc_create_vertical]=false 124 | bar="$(tg_create_linear "$aid" params "$layout")" 125 | unset "params[$tgc_create_vertical]" 126 | 127 | tg_view_height "$aid" "$bar" "$tgc_view_wrap_content" 128 | tg_view_linear "$aid" "$bar" 0 129 | 130 | alayout="$(tg_create_linear "$aid" params "$bar")" 131 | vlayout="$(tg_create_linear "$aid" params "$bar")" 132 | 133 | tg_view_width "$aid" "$alayout" 0 134 | tg_view_width "$aid" "$vlayout" 0 135 | 136 | aspin="$(tg_create_spinner "$aid" params "$alayout")" 137 | vspin="$(tg_create_spinner "$aid" params "$vlayout")" 138 | 139 | tg_view_list "$aid" "$aspin" aformatsarray 140 | tg_view_list "$aid" "$vspin" vformatsarray 141 | 142 | asize="$(tg_create_text "$aid" params "$alayout")" 143 | vsize="$(tg_create_text "$aid" params "$vlayout")" 144 | 145 | params[$tgc_create_text]="Download" 146 | download="$(tg_create_button "$aid" params "$layout")" 147 | tg_view_height "$aid" "$download" "$tgc_view_wrap_content" 148 | tg_view_linear "$aid" "$download" 0 149 | unset "params[$tgc_create_text]" 150 | 151 | acodec="" 152 | vcodec="" 153 | 154 | while true; do 155 | ev="$(tg_msg_recv_event_blocking)" 156 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_destroy" ]; then 157 | exit 0 158 | fi 159 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_item_selected" ]; then 160 | # Handle format selection 161 | if [ "$(tg_event_id "$ev")" = "$aspin" ]; then 162 | sel="$(echo "$ev" | jq -r '.value.selected')" 163 | acodec="$(echo "$aformats" | jq -r --arg sel "$sel" 'select(.format == $sel).format_id')" 164 | size="$(echo "$aformats" | jq --arg sel "$sel" 'select(.format == $sel).filesize')" 165 | tg_view_text "$aid" "$asize" "$((size / 1000 / 1000)) MB" 166 | fi 167 | if [ "$(tg_event_id "$ev")" = "$vspin" ]; then 168 | sel="$(echo "$ev" | jq -r '.value.selected')" 169 | vcodec="$(echo "$vformats" | jq -r --arg sel "$sel" 'select(.format == $sel).format_id')" 170 | size="$(echo "$vformats" | jq --arg sel "$sel" 'select(.format == $sel).filesize')" 171 | tg_view_text "$aid" "$vsize" "$((size / 1000 / 1000)) MB" 172 | # Replace file extension automatically 173 | filename="$(tg_view_get_text "$aid" "$filenameedit")" 174 | filename="${filename%.*}.$(echo "$vformats" | jq -r --arg sel "$sel" 'select(.format == $sel).ext')" 175 | tg_view_text "$aid" "$filenameedit" "$filename" 176 | fi 177 | fi 178 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_id "$ev")" = "$download" ] && [ "$acodec" ] && [ "$vcodec" ]; then 179 | filename="$(tg_view_get_text "$aid" "$filenameedit")" 180 | # Close the connection to the plugin here, or else it will persist through exec 181 | eval "$(trap -p EXIT | cut -d "'" -f 2)" 182 | echo youtube-dl -f "'$vcodec+$acodec'" -o "'$filename'" "'$1'" 183 | exec youtube-dl -f "$vcodec+$acodec" -o "$filename" "$1" 184 | fi 185 | 186 | done 187 | 188 | -------------------------------------------------------------------------------- /gendocHTML.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pandoc -f gfm -t html -o doc_html/manual-dark.html --metadata title="termux-gui-bash Manual" --self-contained --template doc_templates/doctemplate.html --css doc_templates/github-markdown-dark.css Manual.md 4 | pandoc -f gfm -t html -o doc_html/manual-light.html --metadata title="termux-gui-bash Manual" --self-contained --template doc_templates/doctemplate.html --css doc_templates/github-markdown-light.css Manual.md 5 | pandoc -f gfm -t html -o doc_html/tutorial-dark.html --metadata title="termux-gui-bash Tutorial" --self-contained --template doc_templates/doctemplate.html --css doc_templates/github-markdown-dark.css TUTORIAL.md 6 | pandoc -f gfm -t html -o doc_html/tutorial-light.html --metadata title="termux-gui-bash Tutorial" --self-contained --template doc_templates/doctemplate.html --css doc_templates/github-markdown-light.css TUTORIAL.md 7 | 8 | gzip -f doc_html/*.html 9 | -------------------------------------------------------------------------------- /src/docviewer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/tgui-bash 2 | 3 | if [ "$#" != 1 ]; then 4 | exit 1 5 | fi 6 | 7 | # shellcheck disable=SC2034 8 | declare -A aparams=() 9 | declare -a activity=() 10 | 11 | tg_activity_new aparams activity 12 | 13 | aid="${activity[0]}" 14 | 15 | wv="$(tg_create_web "$aid" aparams)" 16 | 17 | tg_web_load_uri "$aid" "$wv" "data:text/html;base64,$(gunzip -c "$1" | base64 -w 0)" 18 | 19 | while true; do 20 | ev="$(tg_msg_recv_event_blocking)" 21 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_destroy" ]; then 22 | exit 0 23 | fi 24 | done 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/helper.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | 23 | 24 | enum errors { 25 | CONNECTION_TERMINATED = 1, 26 | PARAMS_ERROR = 2, 27 | SYSTEM_ERROR = 3, 28 | UID_ERROR = 4, 29 | OOM_ERROR = 5, 30 | }; 31 | 32 | 33 | 34 | void printUsage() { 35 | fputs("Usage: termux-gui.bash-helper [--main] sockname\n", stderr); 36 | exit(PARAMS_ERROR); 37 | } 38 | 39 | void printError(const char* msg) { 40 | perror(msg); 41 | exit(SYSTEM_ERROR); 42 | } 43 | 44 | 45 | 46 | volatile int socket_fd = -1; 47 | pthread_t send_thread_handle; 48 | 49 | #define BUFFERSIZE (4096) 50 | 51 | uint8_t in_buffer[BUFFERSIZE]; 52 | 53 | 54 | uint8_t* out_buffer = NULL; 55 | int out_buffer_size = BUFFERSIZE; 56 | 57 | const uint8_t separator = 30; // ASCII record separator 58 | 59 | 60 | 61 | void read_block(int fd, void* buffer, int size) { 62 | int toread = size; 63 | while (toread > 0) { 64 | int ret = read(fd, buffer + (size - toread), toread); 65 | if (ret <= 0) { 66 | exit(CONNECTION_TERMINATED); 67 | } 68 | toread -= ret; 69 | } 70 | } 71 | 72 | void write_block(int fd, void* buffer, int size) { 73 | int towrite = size; 74 | while (towrite > 0) { 75 | int ret = write(fd, buffer + (size - towrite), towrite); 76 | if (ret <= 0) { 77 | exit(CONNECTION_TERMINATED); 78 | } 79 | towrite -= ret; 80 | } 81 | } 82 | 83 | 84 | 85 | void* send_thread(void* unused) { 86 | out_buffer = calloc(out_buffer_size, 1); 87 | if (out_buffer == NULL) { 88 | fputs("Could not allocate out buffer", stderr); 89 | exit(OOM_ERROR); 90 | } 91 | int towrite = 0; 92 | while (true) { 93 | if (towrite >= out_buffer_size) { 94 | out_buffer_size *= 2; 95 | out_buffer = realloc(out_buffer, out_buffer_size); 96 | if (out_buffer == NULL) { 97 | fputs("Could not allocate out buffer", stderr); 98 | exit(OOM_ERROR); 99 | } 100 | } 101 | int ret = read(0, out_buffer + towrite, 1); 102 | if (ret <= 0) { 103 | exit(CONNECTION_TERMINATED); 104 | } 105 | towrite++; 106 | if (out_buffer[towrite-1] == separator) { 107 | towrite--; // don't write the separator 108 | uint32_t towrite_nw = htonl(towrite); 109 | write_block(socket_fd, &towrite_nw, sizeof(towrite_nw)); 110 | write_block(socket_fd, out_buffer, towrite); 111 | towrite = 0; 112 | } 113 | } 114 | return NULL; 115 | } 116 | 117 | 118 | int min(int a, int b) { 119 | if (a < b) return a; 120 | else return b; 121 | } 122 | 123 | 124 | 125 | int main(int argc, char** argv) { 126 | if (argc <= 1) { 127 | printUsage(); 128 | } 129 | const bool main = strcmp(argv[1], "--main") == 0; 130 | if (main && argc == 2) { 131 | printUsage(); 132 | } 133 | const char* const sockname = argv[argc-1]; 134 | 135 | 136 | 137 | struct sockaddr_un adr; 138 | memset(&adr, 0, sizeof(adr)); 139 | adr.sun_family = AF_UNIX; 140 | 141 | if (strlen(sockname) == 0 || strlen(sockname) >= sizeof(adr.sun_path) - 2) { 142 | printUsage(); 143 | } 144 | 145 | strncpy(adr.sun_path + 1, sockname, sizeof(adr.sun_path) - 2); 146 | 147 | 148 | 149 | int server_fd = socket(AF_UNIX, SOCK_STREAM, 0); 150 | if (server_fd == -1) { 151 | printError("Could not create socket"); 152 | } 153 | 154 | if (bind(server_fd, (const struct sockaddr*) &adr, sizeof(adr) - sizeof(adr.sun_path) + 1 + strlen(sockname)) != 0) { 155 | printError("Could not bind to name"); 156 | } 157 | 158 | if (listen(server_fd, 50) != 0) { 159 | printError("Could not listen"); 160 | } 161 | 162 | socket_fd = accept(server_fd, NULL, NULL); 163 | if (socket_fd == -1) { 164 | printError("Could not accept"); 165 | } 166 | 167 | close(server_fd); 168 | server_fd = -1; 169 | 170 | struct ucred cred; 171 | cred.uid = 1; 172 | socklen_t len = sizeof(cred); 173 | if (getsockopt(socket_fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) { 174 | printError("Could not get peer uid"); 175 | } 176 | 177 | if (cred.uid != getuid()) { 178 | fprintf(stderr, "Refused connection from UID %d\n", cred.uid); 179 | exit(UID_ERROR); 180 | } 181 | 182 | if (main) { 183 | uint8_t byte = 1; 184 | if (write(socket_fd, &byte, 1) <= 0) exit(CONNECTION_TERMINATED); 185 | if (read(socket_fd, &byte, 1) <= 0) exit(CONNECTION_TERMINATED); 186 | if (byte != 0) { 187 | fputs("Protocol negotiation failed\n", stderr); 188 | exit(CONNECTION_TERMINATED); 189 | } 190 | if (pthread_create(&send_thread_handle, NULL, send_thread, NULL) != 0) { 191 | fputs("Could not create send thread\n", stderr); 192 | exit(SYSTEM_ERROR); 193 | } 194 | } 195 | 196 | 197 | while (true) { 198 | // read message size, convert to host byte order 199 | uint32_t size_nw; 200 | read_block(socket_fd, &size_nw, sizeof(size_nw)); 201 | uint32_t size = ntohl(size_nw); 202 | 203 | 204 | // read message, write to stdout 205 | int toread = size; 206 | while (toread > 0) { 207 | int r = min(BUFFERSIZE, toread); 208 | read_block(socket_fd, in_buffer, r); 209 | write_block(1, in_buffer, r); 210 | toread -= r; 211 | } 212 | if (write(1, &separator, 1) <= 0) { 213 | exit(CONNECTION_TERMINATED); 214 | } 215 | } 216 | 217 | 218 | 219 | return 0; 220 | } 221 | 222 | -------------------------------------------------------------------------------- /src/tgui-bash.sh: -------------------------------------------------------------------------------- 1 | #!@TERMUX_PREFIX@/bin/bash 2 | 3 | # unset variables from the extract script 4 | unset archivestart 5 | 6 | if [ $# -ge 1 ] && [ "$1" = "--" ]; then 7 | shift 8 | fi 9 | 10 | if (return 0 2>/dev/null); then 11 | echo 'tgui-bash does not support sourcing' >&2 12 | exit 1 13 | fi 14 | 15 | if [ $# -lt 1 ] && ! (return 0 2>/dev/null); then 16 | echo 'Usage: tgui-bash path ...' >&2 17 | exit 1 18 | fi 19 | 20 | 21 | if [ $# -ge 1 ] && [ "$1" = "-h" ]; then 22 | if [ "$2" = 0 ]; then 23 | docpath="@TERMUX_PREFIX@/share/tgui-bash/manual" 24 | fi 25 | if [ "$2" = 1 ]; then 26 | docpath="@TERMUX_PREFIX@/share/tgui-bash/tutorial" 27 | fi 28 | if [ -z "$docpath" ]; then 29 | while true; do 30 | echo "Showing help. Do you want:" 31 | echo "0: The manual" 32 | echo "1: The tutorial" 33 | read -r resp 34 | if [ "$resp" == 0 ]; then 35 | docpath="@TERMUX_PREFIX@/share/tgui-bash/manual" 36 | break 37 | fi 38 | if [ "$resp" == 1 ]; then 39 | docpath="@TERMUX_PREFIX@/share/tgui-bash/tutorial" 40 | break 41 | fi 42 | echo 43 | done 44 | fi 45 | if [ "$3" = 0 ]; then 46 | docpathext="-dark.html.gz" 47 | fi 48 | if [ "$3" = 1 ]; then 49 | docpathext="-light.html.gz" 50 | fi 51 | if [ -z "$docpathext" ]; then 52 | while true; do 53 | echo "Light or dark theme:" 54 | echo "0: Dark" 55 | echo "1: Light" 56 | read -r resp 57 | if [ "$resp" == 0 ]; then 58 | docpathext="-dark.html.gz" 59 | break 60 | fi 61 | if [ "$resp" == 1 ]; then 62 | docpathext="-light.html.gz" 63 | break 64 | fi 65 | echo 66 | done 67 | fi 68 | if [ "$4" = 0 ]; then 69 | @TERMUX_PREFIX@/share/tgui-bash/docviewer.sh "$docpath$docpathext" 70 | exit 0 71 | fi 72 | if [ "$4" = 1 ]; then 73 | browser="com.android.htmlviewer/.HTMLViewerActivity" 74 | fi 75 | if [ "$4" = 2 ]; then 76 | browser="com.android.chrome/com.google.android.apps.chrome.Main" 77 | fi 78 | if [ "$4" = 3 ]; then 79 | browser="org.mozilla.firefox/.org.mozilla.firefox.App" 80 | fi 81 | if [ -z "$browser" ]; then 82 | while true; do 83 | echo "What browser do you want:" 84 | echo "0: The Termux:GUI WebView" 85 | echo "1: Android integrated HTML Viewer" 86 | echo "2: Chrome" 87 | echo "3: Firefox" 88 | read -r resp 89 | if [ "$resp" == 0 ]; then 90 | @TERMUX_PREFIX@/share/tgui-bash/manual.html "$docpath$docpathext" 91 | exit 0 92 | fi 93 | if [ "$resp" == 1 ]; then 94 | browser="com.android.htmlviewer/.HTMLViewerActivity" 95 | break 96 | fi 97 | if [ "$resp" == 2 ]; then 98 | browser="com.android.chrome/com.google.android.apps.chrome.Main" 99 | break 100 | fi 101 | if [ "$resp" == 3 ]; then 102 | browser="org.mozilla.firefox/.org.mozilla.firefox.App" 103 | break 104 | fi 105 | echo 106 | done 107 | fi 108 | am start -d "data:text/html;base64,$(gunzip -c "$docpath$docpathext" | base64 -w 0)" "$browser" >/dev/null 2>&1 109 | exit 0 110 | fi 111 | 112 | 113 | # Check if jq is installed 114 | if ! command -v jq &>/dev/null; then 115 | echo "jq not installed. Please install jq with 'pkg i jq'" >&2 116 | exit 1 117 | fi 118 | 119 | 120 | 121 | 122 | 123 | function tg__hex_to_dec() { 124 | echo "$((16#${1}))" 125 | } 126 | 127 | 128 | 129 | 130 | 131 | ### CONSTANTS 132 | 133 | declare -r tgc_activity_tid="tid" 134 | # shellcheck disable=SC2034 135 | declare -r tgc_activity_dialog="dialog" 136 | # shellcheck disable=SC2034 137 | declare -r tgc_activity_canceloutside="canceloutside" 138 | # shellcheck disable=SC2034 139 | declare -r tgc_activity_pip="pip" 140 | # shellcheck disable=SC2034 141 | declare -r tgc_activity_lockscreen="lockscreen" 142 | # shellcheck disable=SC2034 143 | declare -r tgc_activity_overlay="overlay" 144 | # shellcheck disable=SC2034 145 | declare -r tgc_activity_intercept="intercept" 146 | 147 | 148 | # shellcheck disable=SC2034 149 | declare -r tgc_create_text="text" 150 | # shellcheck disable=SC2034 151 | declare -r tgc_create_selectable_text="selectableText" 152 | # shellcheck disable=SC2034 153 | declare -r tgc_create_clickable_links="clickableLinks" 154 | # shellcheck disable=SC2034 155 | declare -r tgc_create_vertical="vertical" 156 | # shellcheck disable=SC2034 157 | declare -r tgc_create_snapping="snapping" 158 | # shellcheck disable=SC2034 159 | declare -r tgc_create_fill_viewport="fillviewport" 160 | # shellcheck disable=SC2034 161 | declare -r tgc_create_no_bar="nobar" 162 | # shellcheck disable=SC2034 163 | declare -r tgc_create_checked="checked" 164 | # shellcheck disable=SC2034 165 | declare -r tgc_create_single_line="singleline" 166 | # shellcheck disable=SC2034 167 | declare -r tgc_create_line="line" 168 | # shellcheck disable=SC2034 169 | declare -r tgc_create_type="type" 170 | # shellcheck disable=SC2034 171 | declare -r tgc_create_rows="rows" 172 | # shellcheck disable=SC2034 173 | declare -r tgc_create_cols="cols" 174 | # shellcheck disable=SC2034 175 | declare -r tgc_create_all_caps="allcaps" 176 | 177 | 178 | # shellcheck disable=SC2034 179 | declare -r tgc_vis_gone="0" 180 | # shellcheck disable=SC2034 181 | declare -r tgc_vis_visible="2" 182 | # shellcheck disable=SC2034 183 | declare -r tgc_vis_hidden="1" 184 | 185 | # shellcheck disable=SC2034 186 | declare -r tgc_view_wrap_content='"WRAP_CONTENT"' 187 | # shellcheck disable=SC2034 188 | declare -r tgc_view_match_parent='"MATCH_PARENT"' 189 | 190 | # shellcheck disable=SC2034 191 | declare -r tgc_grid_top="top" 192 | # shellcheck disable=SC2034 193 | declare -r tgc_grid_bottom="bottom" 194 | # shellcheck disable=SC2034 195 | declare -r tgc_grid_left="left" 196 | # shellcheck disable=SC2034 197 | declare -r tgc_grid_right="right" 198 | # shellcheck disable=SC2034 199 | declare -r tgc_grid_center="center" 200 | # shellcheck disable=SC2034 201 | declare -r tgc_grid_baseline="baseline" 202 | # shellcheck disable=SC2034 203 | declare -r tgc_grid_fill="fill" 204 | 205 | # shellcheck disable=SC2034 206 | declare -r tgc_dir_top="top" 207 | # shellcheck disable=SC2034 208 | declare -r tgc_dir_bottom="bottom" 209 | # shellcheck disable=SC2034 210 | declare -r tgc_dir_left="left" 211 | # shellcheck disable=SC2034 212 | declare -r tgc_dir_right="right" 213 | 214 | # shellcheck disable=SC2034 215 | declare -r tgc_grav_top_left="0" 216 | # shellcheck disable=SC2034 217 | declare -r tgc_grav_center="1" 218 | # shellcheck disable=SC2034 219 | declare -r tgc_grav_bottom_right="2" 220 | 221 | 222 | # shellcheck disable=SC2034 223 | declare -r tgc_not_id="id" 224 | # shellcheck disable=SC2034 225 | declare -r tgc_not_ongoing="ongoing" 226 | # shellcheck disable=SC2034 227 | declare -r tgc_not_layout="layout" 228 | # shellcheck disable=SC2034 229 | declare -r tgc_not_expanded_layout="expandedLayout" 230 | # shellcheck disable=SC2034 231 | declare -r tgc_not_hud_layout="hudLayout" 232 | # shellcheck disable=SC2034 233 | declare -r tgc_not_title="title" 234 | # shellcheck disable=SC2034 235 | declare -r tgc_not_content="content" 236 | # shellcheck disable=SC2034 237 | declare -r tgc_not_large_image="largeImage" 238 | # shellcheck disable=SC2034 239 | declare -r tgc_not_large_text="largeText" 240 | # shellcheck disable=SC2034 241 | declare -r tgc_not_large_image_thumbnail="largeImageAsThumbnail" 242 | # shellcheck disable=SC2034 243 | declare -r tgc_not_icon="icon" 244 | # shellcheck disable=SC2034 245 | declare -r tgc_not_alert_once="alertOnce" 246 | # shellcheck disable=SC2034 247 | declare -r tgc_not_show_timestamp="showTimestamp" 248 | # shellcheck disable=SC2034 249 | declare -r tgc_not_timestamp="timestamp" 250 | # shellcheck disable=SC2034 251 | declare -r tgc_not_actions="actions" 252 | 253 | 254 | # shellcheck disable=SC2034 255 | declare -r tgc_ev_click="click" 256 | # shellcheck disable=SC2034 257 | declare -r tgc_ev_long_click="longClick" 258 | # shellcheck disable=SC2034 259 | declare -r tgc_ev_focus_change="focusChange" 260 | # shellcheck disable=SC2034 261 | declare -r tgc_ev_refresh="refresh" 262 | # shellcheck disable=SC2034 263 | declare -r tgc_ev_selected="selected" 264 | # shellcheck disable=SC2034 265 | declare -r tgc_ev_item_selected="itemselected" 266 | # shellcheck disable=SC2034 267 | declare -r tgc_ev_text="text" 268 | # shellcheck disable=SC2034 269 | declare -r tgc_ev_back="back" 270 | # shellcheck disable=SC2034 271 | declare -r tgc_ev_webview_navigation="webviewNavigation" 272 | # shellcheck disable=SC2034 273 | declare -r tgc_ev_webview_http_error="webviewHTTPError" 274 | # shellcheck disable=SC2034 275 | declare -r tgc_ev_webview_error="webviewError" 276 | # shellcheck disable=SC2034 277 | declare -r tgc_ev_webview_destroyed="webviewDestroyed" 278 | # shellcheck disable=SC2034 279 | declare -r tgc_ev_webview_progress="webviewProgress" 280 | # shellcheck disable=SC2034 281 | declare -r tgc_ev_webview_console_message="webviewConsoleMessage" 282 | # shellcheck disable=SC2034 283 | declare -r tgc_ev_create="create" 284 | # shellcheck disable=SC2034 285 | declare -r tgc_ev_start="start" 286 | # shellcheck disable=SC2034 287 | declare -r tgc_ev_resume="resume" 288 | # shellcheck disable=SC2034 289 | declare -r tgc_ev_pause="pause" 290 | # shellcheck disable=SC2034 291 | declare -r tgc_ev_stop="stop" 292 | # shellcheck disable=SC2034 293 | declare -r tgc_ev_destroy="destroy" 294 | # shellcheck disable=SC2034 295 | declare -r tgc_ev_config="config" 296 | # shellcheck disable=SC2034 297 | declare -r tgc_ev_user_leave_hint="UserLeaveHint" 298 | # shellcheck disable=SC2034 299 | declare -r tgc_ev_pip_changed="pipchanged" 300 | # shellcheck disable=SC2034 301 | declare -r tgc_ev_airplane="airplane" 302 | # shellcheck disable=SC2034 303 | declare -r tgc_ev_locale="locale" 304 | # shellcheck disable=SC2034 305 | declare -r tgc_ev_screen_on="screen_on" 306 | # shellcheck disable=SC2034 307 | declare -r tgc_ev_screen_off="screen_off" 308 | # shellcheck disable=SC2034 309 | declare -r tgc_ev_timezone="timezone" 310 | # shellcheck disable=SC2034 311 | declare -r tgc_ev_notification="notification" 312 | # shellcheck disable=SC2034 313 | declare -r tgc_ev_notification_dismissed="notificationDismissed" 314 | # shellcheck disable=SC2034 315 | declare -r tgc_ev_notification_action="notificationaction" 316 | # shellcheck disable=SC2034 317 | declare -r tgc_ev_remote_click="remoteclick" 318 | 319 | 320 | ### MESSAGE FUNCTIONS 321 | 322 | # receives a message from the main 323 | function tg_msg_recv() { 324 | declare -r rs=$'\x1e' # ASCII RS 325 | local buf 326 | IFS= read -r -s -d "$rs" -u "${tg__main[0]}" buf 327 | local ret=$? 328 | if [ $ret -ne 0 ]; then 329 | return $ret 330 | fi 331 | echo -n "$buf" 332 | return 0 333 | } 334 | 335 | # receives a message from the event socket 336 | function tg_msg_recv_event_blocking() { 337 | declare -r rs=$'\x1e' # ASCII RS 338 | local buf 339 | IFS= read -r -s -d "$rs" -u "${tg__event[0]}" buf 340 | local ret=$? 341 | if [ $ret -ne 0 ]; then 342 | return $ret 343 | fi 344 | echo -n "$buf" 345 | return 0 346 | } 347 | 348 | # receives an event if one is available 349 | function tg_msg_recv_event() { 350 | if IFS= read -t 0 -r -s -d "$rs" -u "${tg__event[0]}"; then 351 | tg_msg_recv_event_blocking 352 | fi 353 | } 354 | 355 | # sends a raw string to the plugin 356 | function tg_msg_send() { 357 | declare -r rs=$'\x1e' # ASCII RS 358 | echo -n "${1}${rs}" >&"${tg__main[1]}" 359 | if [ ! -e "/proc/$$/${tg__main[1]}" ]; then 360 | return 1 361 | fi 362 | return 0 363 | } 364 | 365 | # Quotes a string with double quotes and escapes all double quotes in the string. 366 | function tg_str_quote() { 367 | # escape backslashes 368 | local backslashes_escaped="${1//\\/\\\\}" 369 | 370 | # escape quotes 371 | local quotes_escaped="${backslashes_escaped//\"/\\\"}" 372 | 373 | # escape newlines 374 | local newlines_escaped="${quotes_escaped//$'\n'/\\n}" 375 | 376 | # escape tabs 377 | local tabs_escaped="${newlines_escaped//$'\t'/\\t}" 378 | 379 | # Quote and output 380 | echo -n '"'"$tabs_escaped"'"' 381 | } 382 | 383 | # Sends a message to the plugin. 384 | # The first parameter is the message type. 385 | # The second parameter is an associative array name that contains the parameters. 386 | # String parameters have to be quoted with tg_str_quote. 387 | function tg_json_send() { 388 | local tg_json_send_tosend='{"method": "'"$1"'", "params": {' 389 | if [ "$2" != "" ]; then 390 | local -n tg_json_send_params="$2" 391 | if [ "${#tg_json_send_params[@]}" -ne 0 ]; then 392 | for key in "${!tg_json_send_params[@]}"; do 393 | tg_json_send_tosend="${tg_json_send_tosend}"'"'"$key"'":'"${tg_json_send_params[$key]}," 394 | done 395 | tg_json_send_tosend=${tg_json_send_tosend::-1}"}}" 396 | else 397 | tg_json_send_tosend=${tg_json_send_tosend}"}}" 398 | fi 399 | else 400 | tg_json_send_tosend=${tg_json_send_tosend}"}}" 401 | fi 402 | #echo "$tg_json_send_tosend" 403 | tg_msg_send "$tg_json_send_tosend" 404 | } 405 | 406 | function tg__array_copy() { 407 | local -n tg__array_copy_source="$1" 408 | local -n tg__array_copy_dest="$2" 409 | for key in "${!tg__array_copy_source[@]}"; do 410 | # shellcheck disable=SC2034 411 | tg__array_copy_dest["$key"]="${tg__array_copy_source["$key"]}" 412 | done 413 | } 414 | 415 | 416 | 417 | 418 | ### GLOBAL METHODS 419 | 420 | 421 | function tg_global_turn_screen_on() { 422 | tg_json_send turnScreenOn 423 | } 424 | 425 | function tg_global_is_locked() { 426 | tg_json_send isLocked 427 | tg_msg_recv | jq -e '. == true' >/dev/null 428 | } 429 | 430 | function tg_global_version() { 431 | tg_json_send getVersion 432 | tg_msg_recv | jq -r '.' 433 | } 434 | 435 | ### ACTIVITY METHODS 436 | 437 | 438 | function tg_activity_new() { 439 | local -n tg_activity_new_params="$1" 440 | local -n tg_activity_new_ret="$2" 441 | tg_json_send "newActivity" "$1" 442 | local tg_activity_rec 443 | tg_activity_rec="$(tg_msg_recv)" 444 | if [ ! -v "tg_activity_new_params[${tgc_activity_tid}]" ]; then 445 | tg_activity_new_ret[0]="$(echo "$tg_activity_rec" | jq -r '.[0]')" 446 | tg_activity_new_ret[1]="$(echo "$tg_activity_rec" | jq -r '.[1]')" 447 | else 448 | # shellcheck disable=SC2034 449 | tg_activity_new_ret[0]="$(echo "$tg_activity_rec" | jq -r '.')" 450 | fi 451 | } 452 | 453 | function tg_activity_finish() { 454 | declare -A tg__local_params=([aid]="$1") 455 | tg_json_send "finishActivity" tg__local_params 456 | } 457 | 458 | function tg_activity_to_back() { 459 | declare -A tg__local_params=([aid]="$1") 460 | tg_json_send "moveTaskToBack" tg__local_params 461 | } 462 | 463 | function tg_activity_theme() { 464 | declare -A tg__local_params=([aid]="$1" [statusBarColor]="$(tg__hex_to_dec "$2")" [colorPrimary]="$(tg__hex_to_dec "$3")" [windowBackground]="$(tg__hex_to_dec "$4")" [textColor]="$(tg__hex_to_dec "$5")" [colorAccent]="$(tg__hex_to_dec "$6")") 465 | tg_json_send "setTheme" tg__local_params 466 | } 467 | 468 | function tg_activity_description() { 469 | declare -A tg__local_params=([aid]="$1" [label]="$2" [img]="$3") 470 | tg_json_send "setTaskDescription" tg__local_params 471 | } 472 | 473 | function tg_activity_pip_tg__local_params() { 474 | declare -A tg__local_params=([aid]="$1" [num]="$2" [den]="$3") 475 | tg_json_send "setPiPParams" tg__local_params 476 | } 477 | 478 | function tg_activity_input() { 479 | declare -A tg__local_params=([aid]="$1" [mode]="$2") 480 | tg_json_send "setInputMode" tg__local_params 481 | } 482 | 483 | function tg_activity_pip() { 484 | declare -A tg__local_params=([aid]="$1" [pip]="$2") 485 | tg_json_send "setPiPMode" tg__local_params 486 | } 487 | 488 | function tg_activity_pip_auto() { 489 | declare -A tg__local_params=([aid]="$1" [pip]="$2") 490 | tg_json_send "setPiPModeAuto" tg__local_params 491 | } 492 | 493 | function tg_activity_keep_screen_on() { 494 | declare -A tg__local_params=([aid]="$1" [on]="$2") 495 | tg_json_send "keepScreenOn" tg__local_params 496 | } 497 | 498 | function tg_activity_orientation() { 499 | declare -A tg__local_params=([aid]="$1" [orientation]="$2") 500 | tg_json_send "setOrientation" tg__local_params 501 | } 502 | 503 | function tg_activity_position() { 504 | declare -A tg__local_params=([aid]="$1" [x]="$2" [y]="$3") 505 | tg_json_send "setPosition" tg__local_params 506 | } 507 | 508 | function tg_activity_configuration() { 509 | declare -A tg__local_params=([aid]="$1") 510 | tg_json_send "getConfiguration" tg__local_params 511 | tg_msg_recv 512 | } 513 | 514 | function tg_activity_request_unlock() { 515 | declare -A tg__local_params=([aid]="$1") 516 | tg_json_send "requestUnlock" tg__local_params 517 | } 518 | 519 | function tg_activity_hide_soft_keyboard() { 520 | declare -A tg__local_params=([aid]="$1") 521 | tg_json_send "hideSoftKeyboard" tg__local_params 522 | } 523 | 524 | function tg_activity_intercept_back_button() { 525 | declare -A tg__local_params=([aid]="$1" [intercept]="$2") 526 | tg_json_send "interceptBackButton" tg__local_params 527 | } 528 | 529 | ### CONFIGURATION METHODS 530 | 531 | function tg_configuration_dark_mode() { 532 | echo "$1" | jq -r '.dark_mode' 533 | } 534 | 535 | function tg_configuration_country() { 536 | echo "$1" | jq -r '.country' 537 | } 538 | 539 | function tg_configuration_language() { 540 | echo "$1" | jq -r '.language' 541 | } 542 | 543 | function tg_configuration_orientation() { 544 | echo "$1" | jq -r '.orientation' 545 | } 546 | 547 | function tg_configuration_keyboardHidden() { 548 | echo "$1" | jq -r '.keyboardHidden' 549 | } 550 | 551 | function tg_configuration_screenwidth() { 552 | echo "$1" | jq -r '.screenwidth' 553 | } 554 | 555 | function tg_configuration_screenheight() { 556 | echo "$1" | jq -r '.screenheight' 557 | } 558 | 559 | function tg_configuration_fontscale() { 560 | echo "$1" | jq -r '.fontscale' 561 | } 562 | 563 | function tg_configuration_density() { 564 | echo "$1" | jq -r '.density' 565 | } 566 | 567 | 568 | 569 | 570 | ### TASK METHODS 571 | 572 | function tg_task_finish() { 573 | declare -A tg__local_params=([tid]="$1") 574 | tg_json_send "finishTask" tg__local_params 575 | } 576 | 577 | function tg_task_to_front() { 578 | declare -A tg__local_params=([tid]="$1") 579 | tg_json_send "bringTaskToFront" tg__local_params 580 | } 581 | 582 | 583 | 584 | 585 | ### VIEW CREATION 586 | 587 | function tg__create() { 588 | # shellcheck disable=SC2034 589 | local -n tg__create_args="$3" 590 | declare -A tg__create_params=([aid]="$2") 591 | tg__array_copy tg__create_args tg__create_params 592 | if [ -v "4" ]; then 593 | tg__create_params[parent]="$4" 594 | fi 595 | if [ -v "5" ]; then 596 | tg__create_params[visibility]="$5" 597 | fi 598 | if [ -v "tg__create_params[text]" ]; then 599 | tg__create_params[text]="$(tg_str_quote "${tg__create_params[text]}")" 600 | fi 601 | if [ -v "tg__create_params[type]" ]; then 602 | tg__create_params[type]="$(tg_str_quote "${tg__create_params[type]}")" 603 | fi 604 | tg_json_send "$1" tg__create_params 605 | tg_msg_recv 606 | } 607 | 608 | function tg_create_linear() { 609 | tg__create "createLinearLayout" "$@" 610 | } 611 | 612 | function tg_create_frame() { 613 | tg__create "createFrameLayout" "$@" 614 | } 615 | 616 | function tg_create_swipe_refresh() { 617 | tg__create "createSwipeRefreshLayout" "$@" 618 | } 619 | 620 | function tg_create_text() { 621 | tg__create "createTextView" "$@" 622 | } 623 | 624 | function tg_create_edit() { 625 | tg__create "createEditText" "$@" 626 | } 627 | 628 | function tg_create_button() { 629 | tg__create "createButton" "$@" 630 | } 631 | 632 | function tg_create_image() { 633 | tg__create "createImageView" "$@" 634 | } 635 | 636 | function tg_create_space() { 637 | tg__create "createSpace" "$@" 638 | } 639 | 640 | function tg_create_nested_scroll() { 641 | tg__create "createNestedScrollView" "$@" 642 | } 643 | 644 | function tg_create_horizontal_scroll() { 645 | tg__create "createHorizontalScrollView" "$@" 646 | } 647 | 648 | function tg_create_radio() { 649 | tg__create "createRadioButton" "$@" 650 | } 651 | 652 | function tg_create_radio_group() { 653 | tg__create "createRadioGroup" "$@" 654 | } 655 | 656 | function tg_create_checkbox() { 657 | tg__create "createCheckbox" "$@" 658 | } 659 | 660 | function tg_create_toggle() { 661 | tg__create "createToggleButton" "$@" 662 | } 663 | 664 | function tg_create_switch() { 665 | tg__create "createSwitch" "$@" 666 | } 667 | 668 | function tg_create_spinner() { 669 | tg__create "createSpinner" "$@" 670 | } 671 | 672 | function tg_create_progress() { 673 | tg__create "createProgressBar" "$@" 674 | } 675 | 676 | function tg_create_tab() { 677 | tg__create "createTabLayout" "$@" 678 | } 679 | 680 | function tg_create_grid() { 681 | tg__create "createGridLayout" "$@" 682 | } 683 | 684 | function tg_create_web() { 685 | tg__create "createWebView" "$@" 686 | } 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | ### VIEW MANIPULATION 700 | 701 | function tg_view_show_cursor() { 702 | declare -A tg__local_params=([aid]="$1" [id]="$2" [show]="$3") 703 | tg_json_send "showCursor" tg__local_params 704 | } 705 | 706 | function tg_view_linear() { 707 | declare -A tg__local_params=([aid]="$1" [id]="$2") 708 | if [ -v "3" ]; then 709 | tg__local_params[weight]="$3" 710 | fi 711 | if [ -v "4" ]; then 712 | tg__local_params[position]="$4" 713 | fi 714 | tg_json_send "setLinearLayoutParams" tg__local_params 715 | } 716 | 717 | function tg_view_grid() { 718 | declare -A tg__local_params=([aid]="$1" [id]="$2") 719 | if [ -v "3" ]; then 720 | tg__local_params[row]="$3" 721 | fi 722 | if [ -v "4" ]; then 723 | tg__local_params[col]="$4" 724 | fi 725 | if [ -v "5" ]; then 726 | tg__local_params[rowsize]="$5" 727 | fi 728 | if [ -v "6" ]; then 729 | tg__local_params[colsize]="$6" 730 | fi 731 | if [ -v "7" ]; then 732 | tg__local_params[alignmentrow]="$(tg_str_quote "$7")" 733 | fi 734 | if [ -v "8" ]; then 735 | tg__local_params[alignmentcol]="$(tg_str_quote "$8")" 736 | fi 737 | tg_json_send "setGridLayoutParams" tg__local_params 738 | } 739 | 740 | function tg_view_location() { 741 | declare -A tg__local_params=([aid]="$1" [id]="$2" [x]="$3" [y]="$4") 742 | if [ -v "5" ]; then 743 | tg__local_params[dp]="$5" 744 | fi 745 | if [ -v "6" ]; then 746 | tg__local_params[top]="$6" 747 | fi 748 | tg_json_send "setViewLocation" tg__local_params 749 | } 750 | 751 | function tg_view_vis() { 752 | declare -A tg__local_params=([aid]="$1" [id]="$2" [vis]="$3") 753 | tg_json_send "setVisibility" tg__local_params 754 | } 755 | 756 | function tg_view_width() { 757 | declare -A tg__local_params=([aid]="$1" [id]="$2" [width]="$3") 758 | if [ -v "4" ]; then 759 | tg__local_params[px]="$4" 760 | fi 761 | tg_json_send "setWidth" tg__local_params 762 | } 763 | 764 | function tg_view_height() { 765 | declare -A tg__local_params=([aid]="$1" [id]="$2" [height]="$3") 766 | if [ -v "4" ]; then 767 | tg__local_params[px]="$4" 768 | fi 769 | tg_json_send "setHeight" tg__local_params 770 | } 771 | 772 | function tg_view_dimensions() { 773 | declare -A tg__local_params=([aid]="$1" [id]="$2") 774 | tg_json_send "getDimensions" tg__local_params 775 | tg_msg_recv 776 | } 777 | 778 | function tg_view_delete() { 779 | declare -A tg__local_params=([aid]="$1" [id]="$2") 780 | tg_json_send "deleteView" tg__local_params 781 | } 782 | 783 | function tg_view_delete_children() { 784 | declare -A tg__local_params=([aid]="$1" [id]="$2") 785 | tg_json_send "deleteChildren" tg__local_params 786 | } 787 | 788 | function tg_view_margin() { 789 | declare -A tg__local_params=([aid]="$1" [id]="$2" [margin]="$3") 790 | if [ -v "4" ]; then 791 | tg__local_params[dir]="$(tg_str_quote "$4")" 792 | fi 793 | tg_json_send "setMargin" tg__local_params 794 | } 795 | 796 | function tg_view_padding() { 797 | declare -A tg__local_params=([aid]="$1" [id]="$2" [padding]="$3") 798 | if [ -v "4" ]; then 799 | tg__local_params[dir]="$(tg_str_quote "$4")" 800 | fi 801 | tg_json_send "setPadding" tg__local_params 802 | } 803 | 804 | function tg_view_bg_color() { 805 | declare -A tg__local_params=([aid]="$1" [id]="$2" [color]="$(tg__hex_to_dec "$3")") 806 | tg_json_send "setBackgroundColor" tg__local_params 807 | } 808 | 809 | function tg_view_text_color() { 810 | declare -A tg__local_params=([aid]="$1" [id]="$2" [color]="$(tg__hex_to_dec "$3")") 811 | tg_json_send "setTextColor" tg__local_params 812 | } 813 | 814 | function tg_view_progress() { 815 | declare -A tg__local_params=([aid]="$1" [id]="$2" [progress]="$3") 816 | tg_json_send "setProgress" tg__local_params 817 | } 818 | 819 | function tg_view_refreshing() { 820 | declare -A tg__local_params=([aid]="$1" [id]="$2" [refresh]="$3") 821 | tg_json_send "setRefreshing" tg__local_params 822 | } 823 | 824 | function tg_view_text() { 825 | declare -A tg__local_params=([aid]="$1" [id]="$2" [text]="$(tg_str_quote "$3")") 826 | tg_json_send "setText" tg__local_params 827 | } 828 | 829 | function tg_view_gravity() { 830 | declare -A tg__local_params=([aid]="$1" [id]="$2" [horizontal]="$3" [vertical]="$4") 831 | tg_json_send "setGravity" tg__local_params 832 | } 833 | 834 | function tg_view_text_size() { 835 | declare -A tg__local_params=([aid]="$1" [id]="$2" [size]="$3") 836 | tg_json_send "setTextSize" tg__local_params 837 | } 838 | 839 | function tg_view_get_text() { 840 | declare -A tg__local_params=([aid]="$1" [id]="$2") 841 | tg_json_send "getText" tg__local_params 842 | tg_msg_recv | jq -r '.' 843 | } 844 | 845 | function tg_view_checked() { 846 | declare -A tg__local_params=([aid]="$1" [id]="$2" [checked]="$3") 847 | tg_json_send "setChecked" tg__local_params 848 | } 849 | 850 | function tg_view_request_focus() { 851 | declare -A tg__local_params=([aid]="$1" [id]="$2" [forcesoft]="$3") 852 | tg_json_send "requestFocus" tg__local_params 853 | } 854 | 855 | function tg_view_get_scroll() { 856 | declare -A tg__local_params=([aid]="$1" [id]="$2") 857 | tg_json_send "getScrollPosition" tg__local_params 858 | tg_msg_recv 859 | } 860 | 861 | function tg_view_set_scroll() { 862 | declare -A tg__local_params=([aid]="$1" [id]="$2" [x]="$3" [y]="$4") 863 | if [ -v "5" ]; then 864 | tg__local_params[soft]="$5" 865 | fi 866 | tg_json_send "setScrollPosition" tg__local_params 867 | } 868 | 869 | function tg_view_list() { 870 | declare -A tg_view_list_params=([aid]="$1" [id]="$2") 871 | local -n tg_view_list_list="$3" 872 | local tg_view_list_array="[" 873 | for key in "${!tg_view_list_list[@]}"; do 874 | tg_view_list_array="${tg_view_list_array}$(tg_str_quote "${tg_view_list_list["$key"]}")," 875 | done 876 | if [ "${#tg_view_list_list[@]}" ]; then 877 | tg_view_list_array="${tg_view_list_array::-1}]" 878 | else 879 | tg_view_list_array="$tg_view_list_array]" 880 | fi 881 | tg_view_list_params[list]="$tg_view_list_array" 882 | tg_json_send "setList" tg_view_list_params 883 | } 884 | 885 | function tg_view_image() { 886 | declare -A tg__local_params=([aid]="$1" [id]="$2" [img]='"'"$3"'"') 887 | tg_json_send "setImage" tg__local_params 888 | } 889 | 890 | function tg_view_select_tab() { 891 | declare -A tg__local_params=([aid]="$1" [id]="$2" [tab]="$3") 892 | tg_json_send "selectTab" tg__local_params 893 | } 894 | 895 | function tg_view_select_item() { 896 | declare -A tg__local_params=([aid]="$1" [id]="$2" [item]="$3") 897 | tg_json_send "selectItem" tg__local_params 898 | } 899 | 900 | function tg_view_clickable() { 901 | declare -A tg__local_params=([aid]="$1" [id]="$2" [clickable]="$3") 902 | tg_json_send "setClickable" tg__local_params 903 | } 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | ### REMOTE LAYOUTS, WIDGETS & NOTIFICATIONS 919 | 920 | function tg_remote_create_layout() { 921 | declare -A tg__local_params=() 922 | tg_json_send "createRemoteLayout" tg__local_params 923 | tg_msg_recv 924 | } 925 | 926 | function tg_remote_delete_layout() { 927 | declare -A tg__local_params=([rid]="$1") 928 | tg_json_send "deleteRemoteLayout" tg__local_params 929 | } 930 | 931 | function tg_remote_create_frame() { 932 | declare -A tg__local_params=([rid]="$1") 933 | if [ -v "2" ]; then 934 | tg__local_params[parent]="$2" 935 | fi 936 | tg_json_send "addRemoteFrameLayout" tg__local_params 937 | tg_msg_recv 938 | } 939 | 940 | function tg_remote_create_text() { 941 | declare -A tg__local_params=([rid]="$1") 942 | if [ -v "2" ]; then 943 | tg__local_params[parent]="$2" 944 | fi 945 | tg_json_send "addRemoteTextView" tg__local_params 946 | tg_msg_recv 947 | } 948 | 949 | function tg_remote_create_button() { 950 | declare -A tg__local_params=([rid]="$1") 951 | if [ -v "2" ]; then 952 | tg__local_params[parent]="$2" 953 | fi 954 | tg_json_send "addRemoteButton" tg__local_params 955 | tg_msg_recv 956 | } 957 | 958 | function tg_remote_create_image() { 959 | declare -A tg__local_params=([rid]="$1") 960 | if [ -v "2" ]; then 961 | tg__local_params[parent]="$2" 962 | fi 963 | tg_json_send "addRemoteImageView" tg__local_params 964 | tg_msg_recv 965 | } 966 | 967 | function tg_remote_create_progress() { 968 | declare -A tg__local_params=([rid]="$1") 969 | if [ -v "2" ]; then 970 | tg__local_params[parent]="$2" 971 | fi 972 | tg_json_send "addRemoteProgressBar" tg__local_params 973 | tg_msg_recv 974 | } 975 | 976 | function tg_remote_bg_color() { 977 | declare -A tg__local_params=([rid]="$1" [id]="$2" [color]="$(tg__hex_to_dec "$3")") 978 | tg_json_send "setRemoteBackgroundColor" tg__local_params 979 | } 980 | 981 | function tg_remote_progress() { 982 | declare -A tg__local_params=([rid]="$1" [id]="$2" [progress]="$3" [max]=100) 983 | tg_json_send "setRemoteProgressBar" tg__local_params 984 | } 985 | 986 | function tg_remote_text() { 987 | declare -A tg__local_params=([rid]="$1" [id]="$2" [text]="$(tg_str_quote "$3")") 988 | tg_json_send "setRemoteText" tg__local_params 989 | } 990 | 991 | function tg_remote_text_size() { 992 | declare -A tg__local_params=([rid]="$1" [id]="$2" [size]="$3") 993 | if [ -v "4" ]; then 994 | tg__local_params[px]="$4" 995 | fi 996 | tg_json_send "setRemoteTextSize" tg__local_params 997 | } 998 | 999 | function tg_remote_text_color() { 1000 | declare -A tg__local_params=([rid]="$1" [id]="$2" [color]="$(tg__hex_to_dec "$3")") 1001 | tg_json_send "setRemoteTextColor" tg__local_params 1002 | } 1003 | 1004 | function tg_remote_vis() { 1005 | declare -A tg__local_params=([rid]="$1" [id]="$2" [vis]="$3") 1006 | tg_json_send "setRemoteVisibility" tg__local_params 1007 | } 1008 | 1009 | function tg_remote_padding() { 1010 | declare -A tg__local_params=([rid]="$1" [id]="$2") 1011 | if [ -v "3" ]; then 1012 | tg__local_params[top]="$3" 1013 | fi 1014 | if [ -v "4" ]; then 1015 | tg__local_params[right]="$4" 1016 | fi 1017 | if [ -v "5" ]; then 1018 | tg__local_params[bottom]="$5" 1019 | fi 1020 | if [ -v "6" ]; then 1021 | tg__local_params[left]="$6" 1022 | fi 1023 | tg_json_send "setRemotePadding" tg__local_params 1024 | } 1025 | 1026 | function tg_remote_image() { 1027 | declare -A tg__local_params=([rid]="$1" [id]="$2" [img]="$(tg_str_quote "$3")") 1028 | tg_json_send "setRemoteImage" tg__local_params 1029 | } 1030 | 1031 | function tg_widget_layout() { 1032 | declare -A tg__local_params=([rid]="$1" [wid]="$2") 1033 | tg_json_send "setWidgetLayout" tg__local_params 1034 | } 1035 | 1036 | function tg_not_create_channel() { 1037 | declare -A tg__local_params=([id]="$(tg_str_quote "$1")" [importance]="$2" [name]="$(tg_str_quote "$3")") 1038 | tg_json_send "createNotificationChannel" tg__local_params 1039 | } 1040 | 1041 | function tg_not_create() { 1042 | declare -A tg__local_params=([channel]="$(tg_str_quote "$1")" [importance]="$2") 1043 | # shellcheck disable=SC2034 1044 | local -n tg_not_create_args="$3" 1045 | tg__array_copy tg_not_create_args tg__local_params 1046 | if [ -v "tg__local_params[title]" ]; then 1047 | tg__local_params[title]="$(tg_str_quote "${tg__local_params[title]}")" 1048 | fi 1049 | if [ -v "tg__local_params[content]" ]; then 1050 | tg__local_params[content]="$(tg_str_quote "${tg__local_params[content]}")" 1051 | fi 1052 | if [ -v "tg__local_params[largeImage]" ]; then 1053 | tg__local_params[largeImage]="$(tg_str_quote "${tg__local_params[largeImage]}")" 1054 | fi 1055 | if [ -v "tg__local_params[largeText]" ]; then 1056 | tg__local_params[largeText]="$(tg_str_quote "${tg__local_params[largeText]}")" 1057 | fi 1058 | if [ -v "tg__local_params[icon]" ]; then 1059 | tg__local_params[icon]="$(tg_str_quote "${tg__local_params[icon]}")" 1060 | fi 1061 | if [ -v "3" ]; then 1062 | local -n tg_not_create_actions="$3" 1063 | local array="[" 1064 | for key in "${!tg_not_create_actions[@]}"; do 1065 | array="${array}$(tg_str_quote "${tg_not_create_actions["$key"]}")," 1066 | done 1067 | if [ "${#tg_not_create_actions[@]}" ]; then 1068 | array="${array::-1}]" 1069 | else 1070 | array="$array]" 1071 | fi 1072 | tg__local_params[actions]="$array" 1073 | fi 1074 | tg_json_send "createNotification" tg__local_params 1075 | } 1076 | 1077 | function tg_not_cancel() { 1078 | declare -A tg__local_params=([id]="$1") 1079 | tg_json_send "cancelNotification" tg__local_params 1080 | } 1081 | 1082 | ### EVENT CONTROL 1083 | 1084 | 1085 | 1086 | function tg_event_send_click() { 1087 | declare -A tg__local_params=([aid]="$1" [id]="$2" [send]="$3") 1088 | tg_json_send "sendClickEvent" tg__local_params 1089 | } 1090 | 1091 | function tg_event_send_long_click() { 1092 | declare -A tg__local_params=([aid]="$1" [id]="$2" [send]="$3") 1093 | tg_json_send "sendLongClickEvent" tg__local_params 1094 | } 1095 | 1096 | function tg_event_send_focus() { 1097 | declare -A tg__local_params=([aid]="$1" [id]="$2" [send]="$3") 1098 | tg_json_send "sendFocusChangeEvent" tg__local_params 1099 | } 1100 | 1101 | function tg_event_send_touch() { 1102 | declare -A tg__local_params=([aid]="$1" [id]="$2" [send]="$3") 1103 | tg_json_send "sendTouchEvent" tg__local_params 1104 | } 1105 | 1106 | function tg_event_send_text() { 1107 | declare -A tg__local_params=([aid]="$1" [id]="$2" [send]="$3") 1108 | tg_json_send "sendTextEvent" tg__local_params 1109 | } 1110 | 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | ### WEBVIEW 1118 | 1119 | function tg_web_allow_js() { 1120 | declare -A tg__local_params=([aid]="$1" [id]="$2" [allow]="$3") 1121 | tg_json_send "allowJavascript" tg__local_params 1122 | } 1123 | 1124 | function tg_web_allow_content() { 1125 | declare -A tg__local_params=([aid]="$1" [id]="$2" [allow]="$3") 1126 | tg_json_send "allowContentURI" tg__local_params 1127 | } 1128 | 1129 | function tg_web_set_data() { 1130 | declare -A tg__local_params=([aid]="$1" [id]="$2" [doc]="$(tg_str_quote "$3")") 1131 | tg_json_send "setData" tg__local_params 1132 | } 1133 | 1134 | function tg_web_load_uri() { 1135 | declare -A tg__local_params=([aid]="$1" [id]="$2" [uri]="$(tg_str_quote "$3")") 1136 | tg_json_send "loadURI" tg__local_params 1137 | } 1138 | 1139 | function tg_web_allow_navigation() { 1140 | declare -A tg__local_params=([aid]="$1" [id]="$2" [allow]="$3") 1141 | tg_json_send "allowNavigation" tg__local_params 1142 | } 1143 | 1144 | function tg_web_back() { 1145 | declare -A tg__local_params=([aid]="$1" [id]="$2") 1146 | tg_json_send "goBack" tg__local_params 1147 | } 1148 | 1149 | function tg_web_forward() { 1150 | declare -A tg__local_params=([aid]="$1" [id]="$2") 1151 | tg_json_send "goForward" tg__local_params 1152 | } 1153 | 1154 | function tg_web_eval_js() { 1155 | declare -A tg__local_params=([aid]="$1" [id]="$2" [code]="$(tg_str_quote "$3")") 1156 | tg_json_send "evaluateJS" tg__local_params 1157 | } 1158 | 1159 | 1160 | 1161 | ### EVENT HANDLING 1162 | 1163 | 1164 | function tg_event_type() { 1165 | echo "$1" | jq -r '.type' 1166 | } 1167 | 1168 | function tg_event_value() { 1169 | echo "$1" | jq -r '.value' 1170 | } 1171 | 1172 | function tg_event_aid() { 1173 | echo "$1" | jq -r '.value.aid' 1174 | } 1175 | 1176 | function tg_event_id() { 1177 | echo "$1" | jq -r '.value.id' 1178 | } 1179 | 1180 | 1181 | 1182 | 1183 | 1184 | 1185 | 1186 | 1187 | 1188 | 1189 | 1190 | 1191 | 1192 | ### SETUP 1193 | 1194 | 1195 | 1196 | # find helper binary 1197 | helper="@TERMUX_PREFIX@/libexec/termux-gui-bash-helper" 1198 | 1199 | # generate random socket names 1200 | sock_main="$(tr -dc A-Za-z0-9 /dev/null 2>&1; kill -9 $tg__event_PID >/dev/null 2>&1' EXIT 1205 | 1206 | 1207 | 1208 | # start listening on the sockets 1209 | coproc tg__main { $helper --main "$sock_main" ; } 1210 | 1211 | # redirect stderr, save it and restore it, to suppress the warning for running 2 coprocs 1212 | exec 3>&2 2>/dev/null 1213 | 1214 | coproc tg__event { exec 2>&3 3>&- ; $helper "$sock_event" ; } 1215 | 1216 | exec 2>&3 3>&- 1217 | 1218 | # contact plugin 1219 | # use termux-am if available 1220 | if ! termux-am broadcast -n com.termux.gui/.GUIReceiver --es mainSocket "$sock_main" --es eventSocket "$sock_event" >/dev/null 2>&1; then 1221 | am broadcast -n com.termux.gui/.GUIReceiver --es mainSocket "$sock_main" --es eventSocket "$sock_event" >/dev/null 2>&1 1222 | fi 1223 | 1224 | 1225 | 1226 | # clear up variables 1227 | unset helper 1228 | unset sock_main 1229 | unset sock_event 1230 | 1231 | 1232 | tg__user_script="$1" 1233 | shift 1234 | # shellcheck disable=SC1090 1235 | . "$tg__user_script" "$@" 1236 | -------------------------------------------------------------------------------- /src/unzip.sh: -------------------------------------------------------------------------------- 1 | #!@TERMUX_PREFIX@/bin/bash 2 | 3 | archivestart="$(awk '$0 == "__ARCHIVE_START__" {print NR + 1; exit 0}' "${BASH_SOURCE[0]}")" 4 | eval "$(tail -n +"$archivestart" "${BASH_SOURCE[0]}" | zcat)" 5 | 6 | exit 0 7 | __ARCHIVE_START__ -------------------------------------------------------------------------------- /tutorial/dialog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/tgui-bash 2 | 3 | set -u 4 | 5 | 6 | # Let the Activity start as a dialog 7 | declare -A aparams=([$tgc_activity_dialog]="true") 8 | declare -a activity=() 9 | 10 | tg_activity_new aparams activity 11 | 12 | aid="${activity[0]}" 13 | 14 | declare -A params=() 15 | 16 | layout="$(tg_create_linear "$aid" params)" 17 | 18 | # EditText 19 | et="$(tg_create_edit "$aid" params "$layout")" 20 | 21 | params[$tgc_create_text]="Click me!" 22 | echo "${params[@]}" 23 | # Button 24 | bt="$(tg_create_button "$aid" params "$layout")" 25 | 26 | #unset "params[$tgc_create_text]" 27 | declare -a list=("Option 1" "Option 2" "Option 3" "Option 4") 28 | 29 | # Spinner 30 | sp="$(tg_create_spinner "$aid" params "$layout")" 31 | # Set the options 32 | tg_view_list "$aid" "$sp" list 33 | 34 | # Toggle 35 | tg="$(tg_create_toggle "$aid" params "$layout")" 36 | 37 | params[$tgc_create_text]="Switch" 38 | 39 | # Switch 40 | sw="$(tg_create_switch "$aid" params "$layout")" 41 | 42 | 43 | params[$tgc_create_text]="Checkbox" 44 | # CheckBox 45 | cb="$(tg_create_checkbox "$aid" params "$layout")" 46 | 47 | # Group for RadioButtons 48 | rg="$(tg_create_radio_group "$aid" params "$layout")" 49 | 50 | # Create some RadioButtons 51 | # RadioButtons have to be in a RadioGroup to work 52 | params[$tgc_create_text]="RadioButton 1" 53 | rb1="$(tg_create_radio "$aid" params "$rg")" 54 | 55 | params[$tgc_create_text]="RadioButton 2" 56 | rb2="$(tg_create_radio "$aid" params "$rg")" 57 | 58 | params[$tgc_create_text]="RadioButton 3" 59 | rb3="$(tg_create_radio "$aid" params "$rg")" 60 | 61 | 62 | while true; do 63 | ev="$(tg_msg_recv_event_blocking)" 64 | # Exit when the user leaves 65 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_stop" ]; then 66 | exit 0 67 | fi 68 | # Print out the EditText text when the Button gets clicked 69 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_aid "$ev")" = "$aid" ] && [ "$(tg_event_id "$ev")" = "$bt" ]; then 70 | echo "EditText text: '$(tg_view_get_text "$aid" "$et")'" 71 | fi 72 | # Print the RadioButton id that is selected 73 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_selected" ] && [ "$(tg_event_aid "$ev")" = "$aid" ] && [ "$(tg_event_id "$ev")" = "$rg" ]; then 74 | echo "RadioButton pressed: $(echo "$ev" | jq -r '.value.selected')" 75 | fi 76 | # Print the checked state of other button on click 77 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_aid "$ev")" = "$aid" ] && [ "$(tg_event_id "$ev")" = "$cb" ]; then 78 | echo "CheckBox pressed: $(echo "$ev" | jq -r '.value.set')" 79 | fi 80 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_aid "$ev")" = "$aid" ] && [ "$(tg_event_id "$ev")" = "$sw" ]; then 81 | echo "Switch pressed: $(echo "$ev" | jq -r '.value.set')" 82 | fi 83 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_aid "$ev")" = "$aid" ] && [ "$(tg_event_id "$ev")" = "$tg" ]; then 84 | echo "ToggleButton pressed: $(echo "$ev" | jq -r '.value.set')" 85 | fi 86 | # Print the selected Spinner option 87 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_item_selected" ] && [ "$(tg_event_aid "$ev")" = "$aid" ] && [ "$(tg_event_id "$ev")" = "$sp" ]; then 88 | echo "Spinner option: '$(echo "$ev" | jq -r '.value.selected')'" 89 | fi 90 | done 91 | -------------------------------------------------------------------------------- /tutorial/events.sh: -------------------------------------------------------------------------------- 1 | #!/bin/tgui-bash 2 | 3 | set -u 4 | 5 | declare -A aparams=() 6 | declare -a activity=() 7 | 8 | tg_activity_new aparams activity 9 | 10 | aid="${activity[0]}" 11 | 12 | # Parameters for creating a Button, in this case the text. 13 | declare -A bparams=([${tgc_create_text}]="Hello World!") 14 | 15 | # Create the Button and save the id 16 | b="$(tg_create_button "$aid" bparams)" 17 | 18 | while true; do 19 | ev="$(tg_msg_recv_event_blocking)" 20 | 21 | # Print when the button is pressed 22 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_id "$ev")" = "$b" ]; then 23 | echo "Button pressed!" 24 | fi 25 | 26 | # Exit when the Activity is closed 27 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_destroy" ]; then 28 | exit 0 29 | fi 30 | done 31 | -------------------------------------------------------------------------------- /tutorial/hello_world.sh: -------------------------------------------------------------------------------- 1 | #!/bin/tgui-bash 2 | 3 | set -u 4 | 5 | # Parameters for the Activity, can be empty 6 | declare -A aparams=() 7 | # Array to store the Activity id and Task id in 8 | declare -a activity=() 9 | 10 | # Start the Activity 11 | tg_activity_new aparams activity 12 | 13 | # Get the Activity id from the array as a shortcut 14 | aid="${activity[0]}" 15 | 16 | # Parameters for creating a TextView, in this case the initial text. 17 | # For the keys there are constants, to help with IDE autocompletion and 18 | # to make it obvious when you mistyped. 19 | declare -A tparams=([${tgc_create_text}]="Hello World!") 20 | 21 | # Create the TextView 22 | tg_create_text "$aid" tparams 23 | 24 | # Wait 2 seconds before exiting 25 | sleep 2 -------------------------------------------------------------------------------- /tutorial/image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/tgui-bash 2 | 3 | set -u 4 | 5 | # Base64-encoded image of the Termux banner 6 | banner="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" 7 | 8 | # Let the Activity start in picture-in-picture mode 9 | declare -A aparams=([$tgc_activity_pip]="true") 10 | declare -a activity=() 11 | 12 | tg_activity_new aparams activity 13 | 14 | aid="${activity[0]}" 15 | 16 | # set the aspect ratio to that of the image 17 | tg_activity_pip_params "$aid" "320" "180" 18 | 19 | declare -A imgparams=() 20 | 21 | # Create an ImageView and save the id 22 | img="$(tg_create_image "$aid" imgparams)" 23 | 24 | # Set the image 25 | tg_view_image "$aid" "$img" "$banner" 26 | 27 | while true; do 28 | ev="$(tg_msg_recv_event_blocking)" 29 | # Exit when the user leaves 30 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_stop" ]; then 31 | exit 0 32 | fi 33 | done 34 | -------------------------------------------------------------------------------- /tutorial/linearlayout.sh: -------------------------------------------------------------------------------- 1 | #!/bin/tgui-bash 2 | 3 | set -u 4 | 5 | declare -A aparams=() 6 | declare -a activity=() 7 | 8 | tg_activity_new aparams activity 9 | 10 | aid="${activity[0]}" 11 | 12 | 13 | declare -A lparams=() 14 | 15 | # Create the LinearLayout and save the id 16 | layout="$(tg_create_linear "$aid" lparams)" 17 | 18 | # Create Buttons in the Layout 19 | declare -A bparams=() 20 | for i in {1..5}; do 21 | bparams[$tgc_create_text]=$i 22 | tg_create_button "$aid" bparams "$layout" >/dev/null 23 | done 24 | 25 | while true; do 26 | ev="$(tg_msg_recv_event_blocking)" 27 | # Exit when the Activity is closed 28 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_destroy" ]; then 29 | exit 0 30 | fi 31 | done 32 | -------------------------------------------------------------------------------- /tutorial/scroll.sh: -------------------------------------------------------------------------------- 1 | #!/bin/tgui-bash 2 | 3 | set -u 4 | 5 | 6 | # Let the Activity start as a dialog 7 | declare -A aparams=() 8 | declare -a activity=() 9 | 10 | tg_activity_new aparams activity 11 | 12 | aid="${activity[0]}" 13 | 14 | declare -A params=() 15 | 16 | layout="$(tg_create_linear "$aid" params)" 17 | 18 | 19 | # Create a Horizontal LinearLayout 20 | params[$tgc_create_vertical]=false 21 | 22 | bar="$(tg_create_linear "$aid" params "$layout")" 23 | 24 | unset "params[$tgc_create_vertical]" 25 | 26 | # Set the height no the minimum needed 27 | tg_view_height "$aid" "$bar" "$tgc_view_wrap_content" 28 | # Don't let it expand to unused space 29 | tg_view_linear "$aid" "$bar" 0 30 | 31 | # Create 2 Buttons in the bar 32 | params[$tgc_create_text]="Bar button 1" 33 | bt1="$(tg_create_button "$aid" params "$bar")" 34 | 35 | params[$tgc_create_text]="Bar button 2" 36 | bt2="$(tg_create_button "$aid" params "$bar")" 37 | 38 | unset "params[$tgc_create_text]" 39 | 40 | 41 | # Create a NestedScrollView and a LinearLayout in it 42 | sc="$(tg_create_nested_scroll "$aid" params "$layout")" 43 | scl="$(tg_create_linear "$aid" params "$sc")" 44 | 45 | # Create Buttons in the NestedScrollView 46 | for i in {1..30}; do 47 | params[$tgc_create_text]="Button $i" 48 | tg_create_button "$aid" params "$scl" >/dev/null 49 | done 50 | 51 | 52 | while true; do 53 | ev="$(tg_msg_recv_event_blocking)" 54 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_destroy" ]; then 55 | exit 0 56 | fi 57 | done 58 | -------------------------------------------------------------------------------- /tutorial/widget.sh: -------------------------------------------------------------------------------- 1 | #!/bin/tgui-bash 2 | 3 | set -u 4 | 5 | 6 | # Let the Activity start as a dialog 7 | declare -A aparams=() 8 | declare -a activity=() 9 | 10 | tg_activity_new aparams activity 11 | 12 | aid="${activity[0]}" 13 | 14 | declare -A params=() 15 | 16 | layout="$(tg_create_linear "$aid" params)" 17 | 18 | 19 | widfield="$(tg_create_edit "$aid" params "$layout")" 20 | 21 | textfield="$(tg_create_edit "$aid" params "$layout")" 22 | 23 | 24 | params[$tgc_create_text]="Set widget text" 25 | 26 | b="$(tg_create_button "$aid" params "$layout")" 27 | 28 | 29 | while true; do 30 | ev="$(tg_msg_recv_event_blocking)" 31 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_destroy" ]; then 32 | exit 0 33 | fi 34 | if [ "$(tg_event_type "$ev")" = "$tgc_ev_click" ] && [ "$(tg_event_id "$ev")" = "$b" ]; then 35 | # Get the widget id 36 | wid="$(tg_view_get_text "$aid" "$widfield")" 37 | text="$(tg_view_get_text "$aid" "$textfield")" 38 | # Create a remote layout and TextView 39 | rl="$(tg_remote_create_layout)" 40 | rt="$(tg_remote_create_text "$rl")" 41 | # Set the text 42 | tg_remote_text "$rl" "$rt" "$text" 43 | # Set the widget layout and destroy the remote layout again 44 | tg_widget_layout "$rl" "$wid" 45 | tc_remote_delete_layout "$rl" 46 | fi 47 | done 48 | --------------------------------------------------------------------------------