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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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. |
& Options associative array. See the constants for keys.
& 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. |
The Activity id of the Activity.
The status bar color.
The primary color.
The window background color.
The text color.
The accent color
|
68 | | tg_activity_description | Sets the Task description and icon. |
The Activity id of the Activity.
The description (appears unused).
The icon.
|
69 | | tg_activity_pip_params | Sets the aspect ratio for picture-in-picture mode. |
The Activity id of the Activity.
The numerator of the aspect ratio.
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. |
The Activity id of the Activity.
The response option.
|
71 | | tg_activity_pip | Makes an Activity enter or leave picture-in-picture-mode. |
The Activity id of the Activity.
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. |
The Activity id of the Activity.
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. |
The Activity id of the Activity.
Boolean: Whether screen should be kept on.
|
74 | | tg_activity_orientation | Sets the Orientation of the Activity. |
The Activity id of the Activity.
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. |
The Activity id of the Activity.
The x position.
The y position.
|
76 | | tg_activity_configuration | Gets the configuration for the Activity as a string. Get the values with the `tg_configuration_*` functions. |
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. |
|
79 | | tg_activity_intercept_back_button | Sets whether the back button should be intercepted. See the constant `tg_actvivity_intercept` fro more information. |
The Activity id of the Activity.
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. |
Boolean. Whether to show the cursor or not.
|
157 | | tg_view_linear | Sets the LinearLayout parameters for a View in a LinearLayout. |
The weight of the View.
The position of the View.
|
158 | | tg_view_grid | Sets the GridLayout parameters for a View in a GridLayout. |
The starting row of the View.
The starting column of the View.
The amount of rows this View uses.
The amount of columns this View uses
The alignment of the View in the row.
The alignment of the View in the column.
|
159 | | tg_view_location | Sets position of a View. |
The x position.
The y position
Whether to position is in dp or not.
Whether the View should be on top of al others.
|
160 | | tg_view_vis | Sets the visibility of a Vew. |
The visibility of the View.
|
161 | | tg_view_width | Sets the width of the view. |
The width of the View.
Whether the width is in px or not.
|
162 | | tg_view_height | Sets the height of the view. |
The height of the View.
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. |
The margin in dp.
The direction of the margin. Leave empty for all directions.
|
167 | | tg_view_padding | Sets the padding of a View. |
The padding in dp.
The direction of the padding. Leave empty for all directions.
|
168 | | tg_view_bg_color | Sets the background color of a View. |
The color.
|
169 | | tg_view_text_color | Sets the text color of a View. |
The color
|
170 | | tg_view_progress | Sets the progress of a ProgressBar. |
The progress in range from 0 to 100.
|
171 | | tg_view_refreshing | Sets whether a SwipeRefreshLayout is refreshing. |
Whether the SwipeRefreshLayout is refreshing.
|
172 | | tg_view_text | Sets the text of the View. |
The text.
|
173 | | tg_view_gravity | Sets the gravity of the text of the View. |
The horizontal gravity.
The vertical gravity
|
174 | | tg_view_text_size | Sets the text size. |
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. |
Whether the View is checked.
|
177 | | tg_view_request_focus | Focuses a View and opens the soft keyboard if the View has Keyboard input. |
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. |
The x scroll position.
The y scroll position.
Whether the srcoll should be smooth.
|
180 | | tg_view_list | Set the list of a Spinner or TabLayout. |
& The array of tab/item names.
|
181 | | tg_view_image | Sets the image for an ImageView. |
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. |
The tab index, starting from 0.
|
183 | | tg_view_select_item | Selects a item in a Spinner. The corresponding itemselected event is also send. |
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))). |
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. |
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. |
The id of the Layout.
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. |
The id of the Layout.
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. |
The id of the Layout.
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. |
The id of the Layout.
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. |
The id of the Layout.
Optional. The parent if for the View.
|
200 | | tg_remote_bg_color | Sets the background color for a View in a remote Layout. |
The id of the Layout.
The id of the View.
The background color.
|
201 | | tg_remote_progress | Sets values for a ProgressBar in a remote Layout. |
The id of the Layout.
The id of the View.
The progress between 0 and 100.
|
202 | | tg_remote_text | Sets the text of a remote View. |
The id of the Layout.
The id of the View.
The text.
|
203 | | tg_remote_text_size | Sets the text size of a remote View. |
The id of the Layout.
The id of the View.
The text size.
|
204 | | tg_remote_text_color | Sets the Text color for a View in a remote Layout. |
The id of the Layout.
The id of the View.
The text color.
|
205 | | tg_remote_vis | Sets the visibility for a View in a remote Layout. |
The id of the Layout.
The id of the View.
The View visibility.
|
206 | | tg_remote_padding | Sets the padding in pixels for a View in a remote Layout. |
The id of the Layout.
The id of the View.
The padding.
|
207 | | tg_remote_image | Sets the image of a remote ImageView. |
The id of the Layout.
The id of the View.
The image.
|
208 | | tg_widget_layout | Sets the layout of a Widget to a remote Layout. |
The id of the Layout.
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. |
The id string of the channel.
The importance of the channel.
The user-visible name.
|
210 | | tg_not_create | Creates a notification. Returns the notification id used for further calls. |
The channel id.
The notification importance
& The parameter array. See the `tgc_not_*` constants.
|
211 | | tg_not_cancel | Cancels a notification. |
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. |
Boolean.Whether to allow or not.
|
252 | | tg_web_allow_content | Sets whether content URIs are allowed in the WebView. |
Boolean.Whether to allow or not.
|
253 | | tg_web_set_data | Sets the document. |
The data.
|
254 | | tg_web_load_uri | Loads a specific URI. |
The URI.
|
255 | | tg_web_allow_navigation | Sets whether user/Javascript navigation is allowed in the WebView. |
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. |
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 |
--------------------------------------------------------------------------------