, MIT
4 | # The cross platform WebSocket implementation for SH.
5 | # https://github.com/meefik/websocket.sh
6 |
7 | [ -n "$WS_SHELL" ] || WS_SHELL="sh"
8 | export LANG="C"
9 |
10 | # read pipe as hex without separating and add \x for each byte
11 | split_hex()
12 | {
13 | local hex code
14 | while read -n 2 code
15 | do
16 | if [ -n "$code" ]
17 | then
18 | hex="$hex\x$code"
19 | fi
20 | done
21 | echo -n "$hex"
22 | }
23 |
24 | # get arguments, first argument - 2
25 | get_arg()
26 | {
27 | eval "echo -n \$$1"
28 | }
29 |
30 | # check contains a byte 81 (text data flag) or 82 (binary data flag)
31 | is_packet()
32 | {
33 | echo -n "$1" | grep -q -e $(printf '\x81') -e $(printf '\x82')
34 | }
35 |
36 | # read N bytes from pipe and convert to unsigned decimal 1-byte units (space seporated)
37 | read_dec()
38 | {
39 | dd bs=$1 count=1 2>/dev/null | od -A n -t u1 -w$1
40 | }
41 |
42 | # read pipe and convert to websocket frame
43 | # see RFC6455 "Base Framing Protocol" https://tools.ietf.org/html/rfc6455
44 | ws_send()
45 | {
46 | local data length
47 | while true
48 | do
49 | # Binary frame: 0x82 [length] [data]
50 | # Max length: 00-7D -> 125; 0000-FFFF -> 65535
51 | data=$(dd bs=65535 count=1 2>/dev/null)
52 | length=$(echo -n "$data" | wc -c)
53 | # exit if received 0 bytes
54 | [ "$length" -gt 0 ] || break
55 | if [ "$length" -gt 125 ]
56 | then
57 | printf "\x82\x7E$(printf '%04x' ${length} | split_hex)"
58 | else
59 | printf "\x82\x$(printf '%02x' ${length})"
60 | fi
61 | echo -n "$data"
62 | done
63 | }
64 |
65 | # initialize websocket connection
66 | ws_connect()
67 | {
68 | local line outkey
69 | while read line
70 | do
71 | if printf "%s" "$line" | grep -q $'^\r$'; then
72 | outkey=$(printf "%s" "$sec_websocket_key" | tr '\r' '\n')
73 | outkey="${outkey}258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
74 | outkey=$(printf "%s" "$outkey" | sha1sum | cut -d ' ' -f 1 | printf $(split_hex) | base64)
75 | #outkey=$(printf %s "$outkey" | openssl dgst -binary -sha1 | openssl base64)
76 | printf "HTTP/1.1 101 Switching Protocols\r\n"
77 | printf "Upgrade: websocket\r\n"
78 | printf "Connection: Upgrade\r\n"
79 | printf "Sec-WebSocket-Accept: %s\r\n" "$outkey"
80 | printf "\r\n"
81 | break
82 | else
83 | case "$line" in
84 | Sec-WebSocket-Key*)
85 | sec_websocket_key=$(get_arg 3 $line)
86 | ;;
87 | esac
88 | fi
89 | done
90 | }
91 |
92 | # main loop
93 | ws_server()
94 | {
95 | local flag header length byte i
96 | while IFS= read -n 1 flag
97 | do
98 | # each packet starts at byte 81 or 82
99 | is_packet "$flag" || continue
100 | # read next 5 bytes:
101 | # 1 -> length
102 | # 2-5 -> encoding bytes
103 | header=$(read_dec 5)
104 | # get packet length
105 | let length=$(get_arg 2 $header)-128
106 | [ "$length" -gt 0 -a "$length" -le 125 ] || continue
107 | # read packet
108 | let i=0
109 | for byte in $(read_dec $length)
110 | do
111 | # decoding byte: byte ^ encoding_bytes[i % 4]
112 | let byte=byte^$(get_arg $(($i % 4 + 3)) $header)
113 | printf "\x$(printf '%02x' $byte)"
114 | let i=i+1
115 | done
116 | done | $WS_SHELL 2>&1 | ws_send
117 | }
118 |
119 | # start
120 | ws_connect &&
121 | ws_server
122 |
--------------------------------------------------------------------------------
/app/src/main/assets/bin/arm/busybox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/arm/busybox
--------------------------------------------------------------------------------
/app/src/main/assets/bin/arm/dd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/arm/dd
--------------------------------------------------------------------------------
/app/src/main/assets/bin/arm/e2fsck:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/arm/e2fsck
--------------------------------------------------------------------------------
/app/src/main/assets/bin/arm/mke2fs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/arm/mke2fs
--------------------------------------------------------------------------------
/app/src/main/assets/bin/arm/pkgdetails:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/arm/pkgdetails
--------------------------------------------------------------------------------
/app/src/main/assets/bin/arm/qemu-i386-static:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/arm/qemu-i386-static
--------------------------------------------------------------------------------
/app/src/main/assets/bin/arm/ssl_helper:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/arm/ssl_helper
--------------------------------------------------------------------------------
/app/src/main/assets/bin/arm_64/busybox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/arm_64/busybox
--------------------------------------------------------------------------------
/app/src/main/assets/bin/arm_64/qemu-x86_64-static:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/arm_64/qemu-x86_64-static
--------------------------------------------------------------------------------
/app/src/main/assets/bin/arm_64/ssl_helper:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/arm_64/ssl_helper
--------------------------------------------------------------------------------
/app/src/main/assets/bin/x86/busybox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/x86/busybox
--------------------------------------------------------------------------------
/app/src/main/assets/bin/x86/dd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/x86/dd
--------------------------------------------------------------------------------
/app/src/main/assets/bin/x86/e2fsck:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/x86/e2fsck
--------------------------------------------------------------------------------
/app/src/main/assets/bin/x86/mke2fs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/x86/mke2fs
--------------------------------------------------------------------------------
/app/src/main/assets/bin/x86/pkgdetails:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/x86/pkgdetails
--------------------------------------------------------------------------------
/app/src/main/assets/bin/x86/qemu-arm-static:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/x86/qemu-arm-static
--------------------------------------------------------------------------------
/app/src/main/assets/bin/x86/ssl_helper:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/x86/ssl_helper
--------------------------------------------------------------------------------
/app/src/main/assets/bin/x86_64/busybox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/x86_64/busybox
--------------------------------------------------------------------------------
/app/src/main/assets/bin/x86_64/qemu-aarch64-static:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/x86_64/qemu-aarch64-static
--------------------------------------------------------------------------------
/app/src/main/assets/bin/x86_64/ssl_helper:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/bin/x86_64/ssl_helper
--------------------------------------------------------------------------------
/app/src/main/assets/web/cgi-bin/resize:
--------------------------------------------------------------------------------
1 | #!/system/bin/sh
2 |
3 | echo "Content-type: text/html"
4 | echo ""
5 |
6 | for param in ${QUERY_STRING//&/ }
7 | do
8 | key="${param%=*}"
9 | value="${param#*=}"
10 | eval ${key//[^a-zA-Z0-9_]/}=\"$value\"
11 | done
12 |
13 | if [ "$dev" -a "$rows" -a "$cols" ]
14 | then
15 | if [ -n "$refresh" ]
16 | then
17 | stty -F $dev rows $(($rows-1)) cols $(($cols-1))
18 | fi
19 | stty -F $dev rows $rows cols $cols
20 | fi
21 |
22 | echo ""
23 | echo ""
24 |
--------------------------------------------------------------------------------
/app/src/main/assets/web/cgi-bin/sync:
--------------------------------------------------------------------------------
1 | #!/system/bin/sh
2 |
3 | echo 'Content-Type: application/octet-stream'
4 | echo 'Content-Transfer-Encoding: binary'
5 | echo 'Content-Disposition: attachment; filename="env.tgz"'
6 | echo ''
7 |
8 | tar czf - -C "$ENV_DIR" .
9 |
--------------------------------------------------------------------------------
/app/src/main/assets/web/cgi-bin/terminal:
--------------------------------------------------------------------------------
1 | #!/system/bin/sh
2 |
3 | echo "Content-type: text/html"
4 | echo ""
5 |
6 | let PORT=${HTTP_HOST##*:}+1
7 | nc -l -p ${PORT} -e websocket.sh /dev/null &
8 | cat ../terminal.html
9 |
10 | echo ""
11 | echo ""
12 |
--------------------------------------------------------------------------------
/app/src/main/assets/web/css/style.css:
--------------------------------------------------------------------------------
1 | #terminal {
2 | position: absolute;
3 | top: 0;
4 | bottom: 0;
5 | left: 0;
6 | right: 0;
7 | margin: 0;
8 | padding: 5px;
9 | background-color: black;
10 | }
11 |
12 | /* Custom scroll bar */
13 |
14 | ::-webkit-scrollbar {
15 | width: 10px;
16 | }
17 |
18 | /* Track */
19 |
20 | ::-webkit-scrollbar-track {
21 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
22 | -webkit-border-radius: 10px;
23 | border-radius: 10px;
24 | }
25 |
26 | /* Handle */
27 |
28 | ::-webkit-scrollbar-thumb {
29 | -webkit-border-radius: 10px;
30 | border-radius: 10px;
31 | background: rgba(128, 128, 128, 0.8);
32 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
33 | }
34 |
35 | ::-webkit-scrollbar-thumb:window-inactive {
36 | background: rgba(128, 128, 128, 0.4);
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/assets/web/css/xterm.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2014 The xterm.js authors. All rights reserved.
3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
4 | * https://github.com/chjj/term.js
5 | * @license MIT
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | *
25 | * Originally forked from (with the author's permission):
26 | * Fabrice Bellard's javascript vt100 for jslinux:
27 | * http://bellard.org/jslinux/
28 | * Copyright (c) 2011 Fabrice Bellard
29 | * The original design remains. The terminal itself
30 | * has been extended to include xterm CSI codes, among
31 | * other features.
32 | */
33 |
34 | /**
35 | * Default styles for xterm.js
36 | */
37 |
38 | .xterm {
39 | font-feature-settings: "liga" 0;
40 | position: relative;
41 | user-select: none;
42 | -ms-user-select: none;
43 | -webkit-user-select: none;
44 | }
45 |
46 | .xterm.focus,
47 | .xterm:focus {
48 | outline: none;
49 | }
50 |
51 | .xterm .xterm-helpers {
52 | position: absolute;
53 | top: 0;
54 | /**
55 | * The z-index of the helpers must be higher than the canvases in order for
56 | * IMEs to appear on top.
57 | */
58 | z-index: 5;
59 | }
60 |
61 | .xterm .xterm-helper-textarea {
62 | /*
63 | * HACK: to fix IE's blinking cursor
64 | * Move textarea out of the screen to the far left, so that the cursor is not visible.
65 | */
66 | position: absolute;
67 | opacity: 0;
68 | left: -9999em;
69 | top: 0;
70 | width: 0;
71 | height: 0;
72 | z-index: -5;
73 | /** Prevent wrapping so the IME appears against the textarea at the correct position */
74 | white-space: nowrap;
75 | overflow: hidden;
76 | resize: none;
77 | }
78 |
79 | .xterm .composition-view {
80 | /* TODO: Composition position got messed up somewhere */
81 | background: #000;
82 | color: #FFF;
83 | display: none;
84 | position: absolute;
85 | white-space: nowrap;
86 | z-index: 1;
87 | }
88 |
89 | .xterm .composition-view.active {
90 | display: block;
91 | }
92 |
93 | .xterm .xterm-viewport {
94 | /* On OS X this is required in order for the scroll bar to appear fully opaque */
95 | background-color: #000;
96 | overflow-y: scroll;
97 | cursor: default;
98 | position: absolute;
99 | right: 0;
100 | left: 0;
101 | top: 0;
102 | bottom: 0;
103 | }
104 |
105 | .xterm .xterm-screen {
106 | position: relative;
107 | }
108 |
109 | .xterm .xterm-screen canvas {
110 | position: absolute;
111 | left: 0;
112 | top: 0;
113 | }
114 |
115 | .xterm .xterm-scroll-area {
116 | visibility: hidden;
117 | }
118 |
119 | .xterm-char-measure-element {
120 | display: inline-block;
121 | visibility: hidden;
122 | position: absolute;
123 | top: 0;
124 | left: -9999em;
125 | line-height: normal;
126 | }
127 |
128 | .xterm {
129 | cursor: text;
130 | }
131 |
132 | .xterm.enable-mouse-events {
133 | /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
134 | cursor: default;
135 | }
136 |
137 | .xterm.xterm-cursor-pointer {
138 | cursor: pointer;
139 | }
140 |
141 | .xterm.column-select.focus {
142 | /* Column selection mode */
143 | cursor: crosshair;
144 | }
145 |
146 | .xterm .xterm-accessibility,
147 | .xterm .xterm-message {
148 | position: absolute;
149 | left: 0;
150 | top: 0;
151 | bottom: 0;
152 | right: 0;
153 | z-index: 10;
154 | color: transparent;
155 | }
156 |
157 | .xterm .live-region {
158 | position: absolute;
159 | left: -9999px;
160 | width: 1px;
161 | height: 1px;
162 | overflow: hidden;
163 | }
164 |
165 | .xterm-dim {
166 | opacity: 0.5;
167 | }
168 |
169 | .xterm-underline {
170 | text-decoration: underline;
171 | }
172 |
--------------------------------------------------------------------------------
/app/src/main/assets/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/web/favicon.png
--------------------------------------------------------------------------------
/app/src/main/assets/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Redirecting, please wait...
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/assets/web/js/main.js:
--------------------------------------------------------------------------------
1 | window.onload = function () {
2 |
3 | function resizePty(pty, rows, cols, refresh) {
4 | if (!pty) return;
5 | var xhr = new XMLHttpRequest();
6 | var uri = 'resize?dev=' + pty + '&rows=' + rows + '&cols=' + cols;
7 | if (refresh) uri += "&refresh=1";
8 | xhr.open('GET', uri);
9 | xhr.send();
10 | }
11 |
12 | function blobToText(data, callback) {
13 | var textDecoder = new TextDecoder();
14 | var fileReader = new FileReader();
15 | fileReader.addEventListener('load', function () {
16 | var str = textDecoder.decode(fileReader.result);
17 | callback(str);
18 | });
19 | fileReader.readAsArrayBuffer(data);
20 | }
21 |
22 | function textToBlob(str) {
23 | return new Blob([str]);
24 | }
25 |
26 | function getQueryParams(key, qs) {
27 | qs = qs || window.location.search;
28 | qs = qs.split("+").join(" ");
29 |
30 | var params = {};
31 | var re = /[?&]?([^=]+)=([^&]*)/g;
32 | var tokens = re.exec(qs);
33 |
34 | while (tokens) {
35 | params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
36 | tokens = re.exec(qs);
37 | }
38 |
39 | return key ? params[key] : params;
40 | }
41 |
42 | var pty;
43 |
44 | var protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
45 | var port = parseInt(location.port) + 1;
46 | var socketURL = protocol + location.hostname + ((port) ? (':' + port) : '');
47 | var socket = new WebSocket(socketURL);
48 |
49 | var terminal = new Terminal({
50 | cursorBlink: true,
51 | fontSize: getQueryParams('size') || 16
52 | });
53 | var fitAddon = new FitAddon.FitAddon();
54 | terminal.loadAddon(fitAddon);
55 | terminal.open(document.getElementById('terminal'));
56 | fitAddon.fit();
57 |
58 | socket.addEventListener('message', function (ev) {
59 | blobToText(ev.data, function (str) {
60 | if (!pty) {
61 | var match = str.match(/\/dev\/pts\/\d+/);
62 | if (match) {
63 | pty = match[0];
64 | resizePty(pty, terminal.rows, terminal.cols);
65 | }
66 | }
67 | str = str.replace(/([^\r])\n|\r$/g, '\r\n');
68 | terminal.write(str);
69 | });
70 | });
71 |
72 | terminal.onData(function (data) {
73 | socket.send(textToBlob(data));
74 | });
75 |
76 | terminal.onResize(function (e) {
77 | resizePty(pty, e.rows, e.cols);
78 | });
79 |
80 | window.addEventListener('resize', function () {
81 | fitAddon.fit();
82 | });
83 |
84 | // Hot key for resize: Ctrl + Alt + r
85 | window.addEventListener('keydown', function (e) {
86 | if (e.ctrlKey && e.altKey && e.keyCode == 82) {
87 | resizePty(pty, terminal.rows, terminal.cols, true);
88 | }
89 | });
90 | };
91 |
--------------------------------------------------------------------------------
/app/src/main/assets/web/js/xterm-addon-fit.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(window,function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(){}return e.prototype.activate=function(e){this._terminal=e},e.prototype.dispose=function(){},e.prototype.fit=function(){var e=this.proposeDimensions();if(e&&this._terminal){var t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}},e.prototype.proposeDimensions=function(){if(this._terminal&&this._terminal.element&&this._terminal.element.parentElement){var e=this._terminal._core,t=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(t.getPropertyValue("height")),n=Math.max(0,parseInt(t.getPropertyValue("width"))),o=window.getComputedStyle(this._terminal.element),i=r-(parseInt(o.getPropertyValue("padding-top"))+parseInt(o.getPropertyValue("padding-bottom"))),a=n-(parseInt(o.getPropertyValue("padding-right"))+parseInt(o.getPropertyValue("padding-left")))-e.viewport.scrollBarWidth;return{cols:Math.max(2,Math.floor(a/e._renderService.dimensions.actualCellWidth)),rows:Math.max(1,Math.floor(i/e._renderService.dimensions.actualCellHeight))}}},e}();t.FitAddon=n}])});
2 | //# sourceMappingURL=xterm-addon-fit.js.map
--------------------------------------------------------------------------------
/app/src/main/assets/web/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/assets/web/logo.png
--------------------------------------------------------------------------------
/app/src/main/assets/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Linux Deploy Terminal",
3 | "icons": [
4 | {
5 | "src": "/logo.png",
6 | "sizes": "192x192",
7 | "type": "image/png"
8 | }
9 | ],
10 | "display": "standalone"
11 | }
--------------------------------------------------------------------------------
/app/src/main/assets/web/terminal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Linux Deploy Terminal
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/App.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy;
2 |
3 | import android.app.Application;
4 | import android.app.NotificationChannel;
5 | import android.app.NotificationManager;
6 | import android.os.Build;
7 |
8 | public class App extends Application {
9 |
10 | public static final String SERVICE_CHANNEL_ID = "SERVICE_CHANNEL";
11 |
12 | @Override
13 | public void onCreate() {
14 | super.onCreate();
15 |
16 | // Create notification channels for Oreo and newer
17 | createNotificationChannels();
18 | }
19 |
20 | private void createNotificationChannels() {
21 | // Create the NotificationChannel, but only on API 26+ because
22 | // the NotificationChannel class is new and not in the support library
23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
24 | CharSequence name = getString(R.string.service_notification_channel_name);
25 | String description = getString(R.string.service_notification_channel_description);
26 | int importance = NotificationManager.IMPORTANCE_LOW;
27 | NotificationChannel channel = new NotificationChannel(SERVICE_CHANNEL_ID, name, importance);
28 | channel.setDescription(description);
29 | // Register the channel with the system; you can't change the importance
30 | // or other notification behaviors after this
31 | NotificationManager notificationManager = getSystemService(NotificationManager.class);
32 | notificationManager.createNotificationChannel(channel);
33 | }
34 |
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/ExecService.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 |
6 | import androidx.annotation.NonNull;
7 | import androidx.core.app.JobIntentService;
8 |
9 | public class ExecService extends JobIntentService {
10 |
11 | public static final int JOB_ID = 1;
12 |
13 | public static void enqueueWork(Context context, Intent work) {
14 | enqueueWork(context, ExecService.class, JOB_ID, work);
15 | }
16 |
17 | @Override
18 | protected void onHandleWork(@NonNull Intent intent) {
19 | final String cmd = intent.getStringExtra("cmd");
20 | final String args = intent.getStringExtra("args");
21 | Thread thread = new Thread(() -> {
22 | switch (cmd) {
23 | case "telnetd":
24 | EnvUtils.telnetd(getBaseContext(), args);
25 | break;
26 | case "httpd":
27 | EnvUtils.httpd(getBaseContext(), args);
28 | break;
29 | default:
30 | PrefStore.showNotification(getBaseContext(), null);
31 | EnvUtils.cli(getApplicationContext(), cmd, args);
32 | }
33 | });
34 | thread.start();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/Logger.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy;
2 |
3 | import android.content.Context;
4 |
5 | import java.io.BufferedReader;
6 | import java.io.Closeable;
7 | import java.io.File;
8 | import java.io.FileWriter;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.io.InputStreamReader;
12 | import java.text.SimpleDateFormat;
13 | import java.util.ArrayList;
14 | import java.util.Date;
15 | import java.util.List;
16 | import java.util.Locale;
17 |
18 | import ru.meefik.linuxdeploy.activity.MainActivity;
19 |
20 | public class Logger {
21 |
22 | private static volatile List protocol = new ArrayList<>();
23 | private static char lastChar = '\n';
24 | private static String lastLine = "";
25 |
26 | /**
27 | * Generate timestamp
28 | *
29 | * @return timestamp
30 | */
31 | private static String getTimeStamp() {
32 | return "[" + new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH).format(new Date()) + "] ";
33 | }
34 |
35 | /**
36 | * Append the message to protocol and show
37 | *
38 | * @param c context
39 | * @param msg message
40 | */
41 | private static synchronized void appendMessage(Context c, final String msg) {
42 | if (msg.length() == 0) return;
43 | String out = msg;
44 | boolean timestamp = PrefStore.isTimestamp(c);
45 | int maxLines = PrefStore.getMaxLines(c);
46 | int protocolSize = protocol.size();
47 | if (protocolSize > 0 && lastChar != '\n') {
48 | protocol.remove(protocolSize - 1);
49 | out = lastLine + out;
50 | }
51 | lastChar = out.charAt(out.length() - 1);
52 | String[] lines = out.split("\\n");
53 | for (int i = 0, l = lines.length; i < l; i++) {
54 | lastLine = lines[i];
55 | if (timestamp) protocol.add(getTimeStamp() + lastLine);
56 | else protocol.add(lastLine);
57 | if (protocolSize + i >= maxLines) {
58 | protocol.remove(0);
59 | }
60 | }
61 | // show protocol
62 | show();
63 | }
64 |
65 | /**
66 | * Clear protocol
67 | *
68 | * @param c context
69 | * @return true if success
70 | */
71 | public static boolean clear(Context c) {
72 | protocol.clear();
73 | File logFile = new File(PrefStore.getLogFile(c));
74 | return logFile.delete();
75 | }
76 |
77 | /**
78 | * Size of protocol
79 | *
80 | * @return size
81 | */
82 | public static int size() {
83 | return protocol.size();
84 | }
85 |
86 | /**
87 | * Show log on main activity
88 | */
89 | public static void show() {
90 | MainActivity.showLog(get());
91 | }
92 |
93 | /**
94 | * Get protocol
95 | *
96 | * @return protocol as text
97 | */
98 | private static String get() {
99 | return android.text.TextUtils.join("\n", protocol);
100 | }
101 |
102 | /**
103 | * Append message to protocol
104 | *
105 | * @param c context
106 | * @param msg message
107 | */
108 | static void log(Context c, String msg) {
109 | appendMessage(c, msg);
110 | }
111 |
112 | /**
113 | * Closeable helper
114 | *
115 | * @param c closable object
116 | */
117 | private static void close(Closeable c) {
118 | if (c != null) {
119 | try {
120 | c.close();
121 | } catch (IOException e) {
122 | // e.printStackTrace();
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * Append stream messages to protocol
129 | *
130 | * @param c context
131 | * @param stream stream
132 | */
133 | static void log(Context c, InputStream stream) {
134 | FileWriter writer = null;
135 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))){
136 | if (PrefStore.isLogger(c)) {
137 | writer = new FileWriter(PrefStore.getLogFile(c));
138 | }
139 | int n;
140 | char[] buffer = new char[1024];
141 | while ((n = reader.read(buffer)) != -1) {
142 | String msg = String.valueOf(buffer, 0, n);
143 | appendMessage(c, msg);
144 | if (writer != null) writer.write(msg);
145 | }
146 | } catch (IOException e) {
147 | e.printStackTrace();
148 | } finally {
149 | close(writer);
150 | close(stream);
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/ParamUtils.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | import java.io.BufferedReader;
7 | import java.io.BufferedWriter;
8 | import java.io.File;
9 | import java.io.FileReader;
10 | import java.io.FileWriter;
11 | import java.io.IOException;
12 | import java.util.Arrays;
13 | import java.util.List;
14 | import java.util.Map;
15 | import java.util.TreeMap;
16 |
17 | class ParamUtils {
18 |
19 | private String name;
20 | private List params;
21 |
22 | ParamUtils(String name, String[] params) {
23 | this.name = name;
24 | this.params = Arrays.asList(params);
25 | }
26 |
27 | private static Map readConf(File confFile) {
28 | TreeMap map = new TreeMap<>();
29 |
30 | try (BufferedReader br = new BufferedReader(new FileReader(confFile))) {
31 | String line;
32 | while ((line = br.readLine()) != null) {
33 | if (!line.startsWith("#") && !line.isEmpty()) {
34 | String[] pair = line.split("=");
35 | String key = pair[0];
36 | String value = pair[1];
37 | map.put(key, value.replaceAll("\"", ""));
38 | }
39 | }
40 | } catch (IOException e) {
41 | // Error!
42 | }
43 |
44 | return map;
45 | }
46 |
47 | private static boolean writeConf(Map map, File confFile) {
48 | try (BufferedWriter bw = new BufferedWriter(new FileWriter(confFile))) {
49 | for (Map.Entry entry : map.entrySet()) {
50 | String key = entry.getKey();
51 | String value = entry.getValue();
52 | bw.write(key + "=\"" + value + "\"");
53 | bw.newLine();
54 | }
55 | return true;
56 | } catch (IOException e) {
57 | return false;
58 | }
59 | }
60 |
61 | public String fixOutputParam(Context c, String key, String value) {
62 | return value;
63 | }
64 |
65 | public String get(Context c, String key) {
66 | SharedPreferences pref = c.getSharedPreferences(this.name, Context.MODE_PRIVATE);
67 | int resourceId = PrefStore.getResourceId(c, key, "string");
68 | Map source = pref.getAll();
69 | String defaultValue = "";
70 | if (resourceId > 0) defaultValue = c.getString(resourceId);
71 | Object value = source.get(key);
72 | if (value == null) value = defaultValue;
73 | return fixOutputParam(c, key, value.toString());
74 | }
75 |
76 | public Map get(Context c) {
77 | SharedPreferences pref = c.getSharedPreferences(this.name, Context.MODE_PRIVATE);
78 | Map source = pref.getAll();
79 | Map target = new TreeMap<>();
80 | for (String key : this.params) {
81 | int resourceId = PrefStore.getResourceId(c, key, "string");
82 | String defaultValue = "";
83 | if (resourceId > 0) defaultValue = c.getString(resourceId);
84 | Object value = source.get(key);
85 | if (value == null) value = defaultValue;
86 | target.put(key.toUpperCase(), fixOutputParam(c, key, value.toString()));
87 | }
88 | for (Map.Entry entry : source.entrySet()) {
89 | String key = entry.getKey();
90 | if (!key.matches("^[A-Z0-9_]+$")) continue;
91 | if (!target.containsKey(key)) target.put(key, entry.getValue().toString());
92 | }
93 | return target;
94 | }
95 |
96 | public String fixInputParam(Context c, String key, String value) {
97 | return value;
98 | }
99 |
100 | public void set(Context c, String key, String value) {
101 | SharedPreferences pref = c.getSharedPreferences(this.name, Context.MODE_PRIVATE);
102 | SharedPreferences.Editor prefEditor = pref.edit();
103 | if (value.equals("true") || value.equals("false")) {
104 | prefEditor.putBoolean(key, fixInputParam(c, key, value).equals("true"));
105 | } else {
106 | prefEditor.putString(key, fixInputParam(c, key, value));
107 | }
108 | prefEditor.apply();
109 | }
110 |
111 | public void set(Context c, Map source) {
112 | SharedPreferences pref = c.getSharedPreferences(this.name, Context.MODE_PRIVATE);
113 | SharedPreferences.Editor prefEditor = pref.edit();
114 | for (Map.Entry entry : source.entrySet()) {
115 | String key = entry.getKey();
116 | if (!key.matches("^[A-Z0-9_]+$")) continue;
117 | String value = entry.getValue();
118 | String lowerKey = key.toLowerCase();
119 | if (params.contains(lowerKey)) {
120 | if (value.equals("true") || value.equals("false")) {
121 | prefEditor.putBoolean(lowerKey, fixInputParam(c, lowerKey, value).equals("true"));
122 | } else {
123 | prefEditor.putString(lowerKey, fixInputParam(c, lowerKey, value));
124 | }
125 | } else {
126 | prefEditor.putString(key, value);
127 | }
128 | }
129 | prefEditor.apply();
130 | }
131 |
132 | boolean dump(Context c, File f) {
133 | return writeConf(get(c), f);
134 | }
135 |
136 | boolean restore(Context c, File f) {
137 | clear(c, false);
138 | if (f.exists()) {
139 | set(c, readConf(f));
140 | return true;
141 | }
142 | return false;
143 | }
144 |
145 | void clear(Context c, boolean all) {
146 | SharedPreferences pref = c.getSharedPreferences(this.name, Context.MODE_PRIVATE);
147 | SharedPreferences.Editor prefEditor = pref.edit();
148 | if (all)
149 | prefEditor.clear();
150 | else {
151 | for (Map.Entry entry : pref.getAll().entrySet()) {
152 | String key = entry.getKey();
153 | if (!key.matches("^[A-Z0-9_]+$")) continue;
154 | prefEditor.remove(key);
155 | }
156 | }
157 | prefEditor.apply();
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/PropertiesStore.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy;
2 |
3 | import android.content.Context;
4 | import android.text.TextUtils;
5 |
6 | import java.util.Arrays;
7 | import java.util.List;
8 | import java.util.Set;
9 | import java.util.TreeSet;
10 |
11 | class PropertiesStore extends ParamUtils {
12 |
13 | public static final String name = "properties_conf";
14 | private static final String[] params = {"distrib", "arch", "suite", "source_path",
15 | "target_type", "target_path", "disk_size", "fs_type", "user_name", "user_password",
16 | "privileged_users", "locale", "dns", "net_trigger", "power_trigger", "init", "init_path", "init_level",
17 | "init_user", "init_async", "ssh_port", "ssh_args", "pulse_host", "pulse_port", "graphics",
18 | "vnc_display", "vnc_depth", "vnc_dpi", "vnc_width", "vnc_height", "vnc_args",
19 | "x11_display", "x11_host", "x11_sdl", "x11_sdl_delay", "fb_display", "fb_dev",
20 | "fb_input", "fb_args", "fb_refresh", "fb_freeze", "desktop", "mounts", "include"};
21 |
22 | PropertiesStore() {
23 | super(name, params);
24 | }
25 |
26 | @Override
27 | public String fixOutputParam(Context c, String key, String value) {
28 | switch (key) {
29 | case "user_password":
30 | if (value.isEmpty()) value = PrefStore.generatePassword();
31 | break;
32 | case "vnc_width":
33 | if (value.isEmpty())
34 | value = String.valueOf(Math.max(PrefStore.getScreenWidth(c), PrefStore.getScreenHeight(c)));
35 | break;
36 | case "vnc_height":
37 | if (value.isEmpty())
38 | value = String.valueOf(Math.min(PrefStore.getScreenWidth(c), PrefStore.getScreenHeight(c)));
39 | break;
40 | case "mounts":
41 | if (!get(c, "is_mounts").equals("true")) value = "";
42 | break;
43 | case "include":
44 | Set includes = new TreeSet<>();
45 | includes.add("bootstrap");
46 | if (get(c, "is_init").equals("true")) {
47 | includes.add("init");
48 | } else {
49 | includes.remove("init");
50 | }
51 | if (get(c, "is_ssh").equals("true")) {
52 | includes.add("extra/ssh");
53 | } else {
54 | includes.remove("extra/ssh");
55 | }
56 | if (get(c, "is_pulse").equals("true")) {
57 | includes.add("extra/pulse");
58 | } else {
59 | includes.remove("extra/pulse");
60 | }
61 | if (get(c, "is_gui").equals("true")) {
62 | includes.add("graphics");
63 | includes.add("desktop");
64 | } else {
65 | includes.remove("graphics");
66 | includes.remove("desktop");
67 | }
68 | value = TextUtils.join(" ", includes);
69 | break;
70 | }
71 | return value;
72 | }
73 |
74 | @Override
75 | public String fixInputParam(Context c, String key, String value) {
76 | if (value != null) {
77 | switch (key) {
78 | case "mounts":
79 | if (!value.isEmpty()) set(c, "is_mounts", "true");
80 | break;
81 | case "include":
82 | List includes = Arrays.asList(value.split(" "));
83 | if (includes.contains("init")) set(c, "is_init", "true");
84 | if (includes.contains("extra/ssh")) set(c, "is_ssh", "true");
85 | if (includes.contains("extra/pulse")) set(c, "is_pulse", "true");
86 | if (includes.contains("graphics")) set(c, "is_gui", "true");
87 | break;
88 | }
89 | }
90 | return value;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/RemoveEnvTask.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy;
2 |
3 | import android.app.ProgressDialog;
4 | import android.content.Context;
5 | import android.os.AsyncTask;
6 |
7 | import java.lang.ref.WeakReference;
8 |
9 | public class RemoveEnvTask extends AsyncTask {
10 |
11 | private ProgressDialog dialog;
12 | private WeakReference contextWeakReference;
13 |
14 | public RemoveEnvTask(Context c) {
15 | contextWeakReference = new WeakReference<>(c);
16 | }
17 |
18 | @Override
19 | protected void onPreExecute() {
20 | Context context = contextWeakReference.get();
21 | if (context != null) {
22 | dialog = new ProgressDialog(context);
23 | dialog.setMessage(context.getString(R.string.removing_env_message));
24 | dialog.show();
25 | }
26 | }
27 |
28 | @Override
29 | protected Boolean doInBackground(String... params) {
30 | Context context = contextWeakReference.get();
31 | return context != null ? EnvUtils.removeEnv(context) : null;
32 | }
33 |
34 | @Override
35 | protected void onPostExecute(Boolean success) {
36 | Context context = contextWeakReference.get();
37 | if (context != null) {
38 | if (dialog.isShowing()) dialog.dismiss();
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/SettingsStore.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy;
2 |
3 | import android.content.Context;
4 |
5 | class SettingsStore extends ParamUtils {
6 |
7 | public static final String name = "settings_conf";
8 | private static final String[] params = {"chroot_dir", "profile"};
9 |
10 | SettingsStore() {
11 | super(name, params);
12 | }
13 |
14 | @Override
15 | public String fixOutputParam(Context c, String key, String value) {
16 | return value;
17 | }
18 |
19 | @Override
20 | public String fixInputParam(Context c, String key, String value) {
21 | return value;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/UpdateEnvTask.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy;
2 |
3 | import android.app.ProgressDialog;
4 | import android.content.Context;
5 | import android.os.AsyncTask;
6 | import android.widget.Toast;
7 |
8 | import java.lang.ref.WeakReference;
9 |
10 | public class UpdateEnvTask extends AsyncTask {
11 |
12 | private ProgressDialog dialog;
13 | private WeakReference contextWeakReference;
14 |
15 | public UpdateEnvTask(Context c) {
16 | contextWeakReference = new WeakReference<>(c);
17 | }
18 |
19 | @Override
20 | protected void onPreExecute() {
21 | Context context = contextWeakReference.get();
22 | if (context != null) {
23 | dialog = new ProgressDialog(context);
24 | dialog.setMessage(context.getString(R.string.updating_env_message));
25 | dialog.show();
26 | }
27 | }
28 |
29 | @Override
30 | protected Boolean doInBackground(String... params) {
31 | Context context = contextWeakReference.get();
32 | return context != null ? EnvUtils.updateEnv(context) : null;
33 | }
34 |
35 | @Override
36 | protected void onPostExecute(Boolean success) {
37 | Context context = contextWeakReference.get();
38 | if (context != null) {
39 | if (dialog.isShowing()) dialog.dismiss();
40 | if (!success) {
41 | Toast.makeText(context, R.string.toast_updating_env_error, Toast.LENGTH_SHORT).show();
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/activity/AboutActivity.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.activity;
2 |
3 | import android.os.Bundle;
4 | import android.text.method.LinkMovementMethod;
5 | import android.widget.TextView;
6 |
7 | import androidx.appcompat.app.AppCompatActivity;
8 |
9 | import ru.meefik.linuxdeploy.PrefStore;
10 | import ru.meefik.linuxdeploy.R;
11 |
12 | public class AboutActivity extends AppCompatActivity {
13 |
14 | @Override
15 | public void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | PrefStore.setLocale(this);
18 | setContentView(R.layout.activity_about);
19 | TextView atv = findViewById(R.id.aboutTextView);
20 | atv.setMovementMethod(LinkMovementMethod.getInstance());
21 | TextView vtv = findViewById(R.id.versionView);
22 | vtv.setText(getString(R.string.app_version, PrefStore.getVersion()));
23 | }
24 |
25 | @Override
26 | public void setTheme(int resId) {
27 | super.setTheme(PrefStore.getTheme(this));
28 | }
29 |
30 | @Override
31 | public void onResume() {
32 | super.onResume();
33 | setTitle(R.string.title_activity_about);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/activity/FullscreenActivity.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.activity;
2 |
3 | import android.content.pm.ActivityInfo;
4 | import android.os.Bundle;
5 | import android.view.Surface;
6 | import android.view.Window;
7 | import android.view.WindowManager;
8 |
9 | import androidx.appcompat.app.AppCompatActivity;
10 |
11 | import ru.meefik.linuxdeploy.PrefStore;
12 | import ru.meefik.linuxdeploy.R;
13 |
14 | public class FullscreenActivity extends AppCompatActivity {
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 |
20 | // remove title
21 | supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
22 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
23 | WindowManager.LayoutParams.FLAG_FULLSCREEN);
24 |
25 | setContentView(R.layout.activity_fullscreen);
26 | }
27 |
28 | @Override
29 | public void onResume() {
30 | super.onResume();
31 |
32 | // Screen lock
33 | if (PrefStore.isScreenLock(this))
34 | this.getWindow().addFlags(
35 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
36 | else this.getWindow().clearFlags(
37 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
38 |
39 | // Set screen orientation (freeze)
40 | int rotation = getWindowManager().getDefaultDisplay().getRotation();
41 | switch (rotation) {
42 | case Surface.ROTATION_180:
43 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
44 | break;
45 | case Surface.ROTATION_270:
46 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
47 | break;
48 | case Surface.ROTATION_0:
49 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
50 | break;
51 | case Surface.ROTATION_90:
52 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
53 | break;
54 | }
55 |
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/activity/MountsActivity.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.activity;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.Menu;
6 | import android.view.MenuItem;
7 | import android.view.View;
8 | import android.widget.EditText;
9 |
10 | import androidx.appcompat.app.AlertDialog;
11 | import androidx.appcompat.app.AppCompatActivity;
12 | import androidx.recyclerview.widget.DividerItemDecoration;
13 | import androidx.recyclerview.widget.LinearLayoutManager;
14 | import androidx.recyclerview.widget.RecyclerView;
15 |
16 | import ru.meefik.linuxdeploy.PrefStore;
17 | import ru.meefik.linuxdeploy.R;
18 | import ru.meefik.linuxdeploy.adapter.MountAdapter;
19 | import ru.meefik.linuxdeploy.model.Mount;
20 |
21 | public class MountsActivity extends AppCompatActivity {
22 |
23 | private MountAdapter adapter;
24 |
25 | private void addDialog() {
26 | View view = LayoutInflater.from(this).inflate(R.layout.properties_mounts, null);
27 | EditText inputSrc = view.findViewById(R.id.editTextSrc);
28 | EditText inputTarget = view.findViewById(R.id.editTextTarget);
29 |
30 | new AlertDialog.Builder(this)
31 | .setTitle(R.string.new_mount_title)
32 | .setView(view)
33 | .setPositiveButton(android.R.string.ok,
34 | (dialog, whichButton) -> {
35 | String src = inputSrc.getText().toString()
36 | .replaceAll("[ :]", "_");
37 | String target = inputTarget.getText().toString()
38 | .replaceAll("[ :]", "_");
39 | if (!src.isEmpty()) {
40 | adapter.addMount(new Mount(src, target));
41 | }
42 | })
43 | .setNegativeButton(android.R.string.cancel,
44 | (dialog, whichButton) -> dialog.cancel()).show();
45 | }
46 |
47 | private void editDialog(Mount mount) {
48 | View view = LayoutInflater.from(this).inflate(R.layout.properties_mounts, null);
49 | EditText inputSrc = view.findViewById(R.id.editTextSrc);
50 | EditText inputTarget = view.findViewById(R.id.editTextTarget);
51 |
52 | inputSrc.setText(mount.getSource());
53 | inputSrc.setSelection(mount.getSource().length());
54 |
55 | inputTarget.setText(mount.getTarget());
56 | inputTarget.setSelection(mount.getTarget().length());
57 |
58 | new AlertDialog.Builder(this)
59 | .setTitle(R.string.edit_mount_title)
60 | .setView(view)
61 | .setPositiveButton(android.R.string.ok,
62 | (dialog, whichButton) -> {
63 | String src = inputSrc.getText().toString()
64 | .replaceAll("[ :]", "_");
65 | String target = inputTarget.getText().toString()
66 | .replaceAll("[ :]", "_");
67 | if (!src.isEmpty()) {
68 | mount.setSource(src);
69 | mount.setTarget(target);
70 | adapter.notifyDataSetChanged();
71 | }
72 | })
73 | .setNegativeButton(android.R.string.cancel,
74 | (dialog, whichButton) -> dialog.cancel())
75 | .show();
76 | }
77 |
78 | private void deleteDialog(Mount mount) {
79 | new AlertDialog.Builder(this)
80 | .setTitle(R.string.confirm_mount_discard_title)
81 | .setMessage(R.string.confirm_mount_discard_message)
82 | .setIcon(R.drawable.ic_warning_24dp)
83 | .setPositiveButton(android.R.string.yes,
84 | (dialog, whichButton) -> adapter.removeMount(mount))
85 | .setNegativeButton(android.R.string.no,
86 | (dialog, whichButton) -> dialog.cancel())
87 | .show();
88 | }
89 |
90 | @Override
91 | protected void onCreate(Bundle savedInstanceState) {
92 | super.onCreate(savedInstanceState);
93 | PrefStore.setLocale(this);
94 | setContentView(R.layout.activity_mounts);
95 |
96 | // RecyclerView Adapter
97 | RecyclerView recyclerView = findViewById(R.id.recycler_view);
98 | adapter = new MountAdapter();
99 | adapter.setOnItemClickListener(this::editDialog);
100 | adapter.setOnItemDeleteListener(this::deleteDialog);
101 |
102 | recyclerView.setAdapter(adapter);
103 | recyclerView.setLayoutManager(new LinearLayoutManager(this));
104 | recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
105 | }
106 |
107 | @Override
108 | public void setTheme(int resId) {
109 | super.setTheme(PrefStore.getTheme(this));
110 | }
111 |
112 | @Override
113 | public boolean onCreateOptionsMenu(Menu menu) {
114 | PrefStore.setLocale(this);
115 | getMenuInflater().inflate(R.menu.activity_mounts, menu);
116 | return super.onCreateOptionsMenu(menu);
117 | }
118 |
119 | @Override
120 | public boolean onOptionsItemSelected(MenuItem item) {
121 | if (item.getItemId() == R.id.menu_add) {
122 | addDialog();
123 | return true;
124 | }
125 |
126 | return false;
127 | }
128 |
129 | @Override
130 | public void onResume() {
131 | super.onResume();
132 |
133 | String titleMsg = getString(R.string.title_activity_mounts) + ": "
134 | + PrefStore.getProfileName(this);
135 | setTitle(titleMsg);
136 |
137 | adapter.setMounts(PrefStore.getMountsList(this));
138 | }
139 |
140 | @Override
141 | public void onPause() {
142 | super.onPause();
143 |
144 | PrefStore.setMountsList(this, adapter.getMounts());
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/activity/ProfilesActivity.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.activity;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.view.GestureDetector;
6 | import android.view.LayoutInflater;
7 | import android.view.Menu;
8 | import android.view.MenuItem;
9 | import android.view.MotionEvent;
10 | import android.view.View;
11 | import android.view.View.OnTouchListener;
12 | import android.widget.ArrayAdapter;
13 | import android.widget.EditText;
14 | import android.widget.ListView;
15 |
16 | import androidx.appcompat.app.AlertDialog;
17 | import androidx.appcompat.app.AppCompatActivity;
18 |
19 | import java.io.File;
20 | import java.util.ArrayList;
21 | import java.util.Collections;
22 | import java.util.List;
23 |
24 | import ru.meefik.linuxdeploy.PrefStore;
25 | import ru.meefik.linuxdeploy.R;
26 |
27 | public class ProfilesActivity extends AppCompatActivity implements OnTouchListener {
28 |
29 | private ListView listView;
30 | private List listItems = new ArrayList<>();
31 | private ArrayAdapter adapter;
32 | private GestureDetector gd;
33 |
34 | /**
35 | * Rename conf file associated with the profile
36 | *
37 | * @param c context
38 | * @param oldName old profile name
39 | * @param newName new profile name
40 | * @return true if success
41 | */
42 | public static boolean renameConf(Context c, String oldName, String newName) {
43 | File oldFile = new File(PrefStore.getEnvDir(c) + "/config/" + oldName + ".conf");
44 | File newFile = new File(PrefStore.getEnvDir(c) + "/config/" + newName + ".conf");
45 | return oldFile.renameTo(newFile);
46 | }
47 |
48 | /**
49 | * Remove conf file associated with the profile
50 | *
51 | * @param c context
52 | * @param name profile name
53 | * @return true if success
54 | */
55 | public static boolean removeConf(Context c, String name) {
56 | File confFile = new File(PrefStore.getEnvDir(c) + "/config/" + name + ".conf");
57 | return confFile.exists() && confFile.delete();
58 | }
59 |
60 | /**
61 | * Get list of profiles
62 | *
63 | * @param c context
64 | * @return list of profiles
65 | */
66 | public static List getProfiles(Context c) {
67 | List profiles = new ArrayList<>();
68 | File confDir = new File(PrefStore.getEnvDir(c) + "/config");
69 | File[] profileFiles = confDir.listFiles();
70 |
71 | if (profileFiles != null) {
72 | for (File profileFile : profileFiles) {
73 | if (profileFile.isFile()) {
74 | String filename = profileFile.getName();
75 | int index = filename.lastIndexOf('.');
76 | if (index != -1) filename = filename.substring(0, index);
77 | profiles.add(filename);
78 | }
79 | }
80 | }
81 |
82 | return profiles;
83 | }
84 |
85 | /**
86 | * Get position by key
87 | *
88 | * @param key
89 | * @return position
90 | */
91 | private int getPosition(String key) {
92 | for (int i = 0; i < listItems.size(); i++) {
93 | if (listItems.get(i).equals(key))
94 | return i;
95 | }
96 |
97 | return -1;
98 | }
99 |
100 | private void addDialog() {
101 | View view = LayoutInflater.from(this).inflate(R.layout.edit_text_dialog, null);
102 | EditText input = view.findViewById(R.id.edit_text);
103 |
104 | new AlertDialog.Builder(this)
105 | .setTitle(R.string.new_profile_title)
106 | .setView(view)
107 | .setPositiveButton(android.R.string.ok,
108 | (dialog, whichButton) -> {
109 | String text = input.getText().toString();
110 | if (!text.isEmpty()) {
111 | listItems.add(text.replaceAll("[^A-Za-z0-9_\\-]", "_"));
112 | adapter.notifyDataSetChanged();
113 | }
114 | })
115 | .setNegativeButton(android.R.string.cancel,
116 | (dialog, whichButton) -> dialog.cancel())
117 | .show();
118 | }
119 |
120 | private void editDialog() {
121 | int pos = listView.getCheckedItemPosition();
122 | if (pos >= 0 && pos < listItems.size()) {
123 | String profileOld = listItems.get(pos);
124 |
125 | View view = LayoutInflater.from(this).inflate(R.layout.edit_text_dialog, null);
126 | EditText input = view.findViewById(R.id.edit_text);
127 | input.setText(profileOld);
128 | input.setSelection(input.getText().length());
129 |
130 | new AlertDialog.Builder(this)
131 | .setTitle(R.string.edit_profile_title)
132 | .setView(view)
133 | .setPositiveButton(android.R.string.ok,
134 | (dialog, whichButton) -> {
135 | String text = input.getText().toString();
136 | if (!text.isEmpty()) {
137 | String profileNew = text.replaceAll("[^A-Za-z0-9_\\-]", "_");
138 | if (!profileOld.equals(profileNew)) {
139 | renameConf(getApplicationContext(), profileOld, profileNew);
140 | listItems.set(pos, profileNew);
141 | adapter.notifyDataSetChanged();
142 | }
143 | }
144 | })
145 | .setNegativeButton(android.R.string.cancel,
146 | (dialog, whichButton) -> dialog.cancel())
147 | .show();
148 | }
149 | }
150 |
151 | private void deleteDialog() {
152 | final int pos = listView.getCheckedItemPosition();
153 | if (pos >= 0 && pos < listItems.size()) {
154 | new AlertDialog.Builder(this)
155 | .setTitle(R.string.confirm_profile_discard_title)
156 | .setMessage(R.string.confirm_profile_discard_message)
157 | .setIcon(R.drawable.ic_warning_24dp)
158 | .setPositiveButton(android.R.string.yes,
159 | (dialog, whichButton) -> {
160 | String key = listItems.remove(pos);
161 | int last = listItems.size() - 1;
162 | if (last < 0) listItems.add(getString(R.string.profile));
163 | if (last >= 0 && pos > last)
164 | listView.setItemChecked(last, true);
165 | adapter.notifyDataSetChanged();
166 | removeConf(getApplicationContext(), key);
167 | })
168 | .setNegativeButton(android.R.string.no,
169 | (dialog, whichButton) -> dialog.cancel())
170 | .show();
171 | }
172 | }
173 |
174 | @Override
175 | public void onCreate(Bundle savedInstanceState) {
176 | super.onCreate(savedInstanceState);
177 | PrefStore.setLocale(this);
178 | setContentView(R.layout.activity_profiles);
179 |
180 | // ListView Adapter
181 | listView = findViewById(R.id.profilesView);
182 | adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_single_choice, listItems);
183 | listView.setAdapter(adapter);
184 |
185 | // Initialize the Gesture Detector
186 | listView.setOnTouchListener(this);
187 | gd = new GestureDetector(this,
188 | new GestureDetector.SimpleOnGestureListener() {
189 | @Override
190 | public boolean onDoubleTap(MotionEvent e) {
191 | finish();
192 | return false;
193 | }
194 | });
195 | }
196 |
197 | @Override
198 | public void setTheme(int resId) {
199 | super.setTheme(PrefStore.getTheme(this));
200 | }
201 |
202 | @Override
203 | public boolean onCreateOptionsMenu(Menu menu) {
204 | PrefStore.setLocale(this);
205 | getMenuInflater().inflate(R.menu.activity_profiles, menu);
206 | return super.onCreateOptionsMenu(menu);
207 | }
208 |
209 | @Override
210 | public boolean onOptionsItemSelected(MenuItem item) {
211 | switch (item.getItemId()) {
212 | case R.id.menu_add:
213 | addDialog();
214 | break;
215 | case R.id.menu_edit:
216 | editDialog();
217 | break;
218 | case R.id.menu_delete:
219 | deleteDialog();
220 | break;
221 | default:
222 | return super.onOptionsItemSelected(item);
223 | }
224 |
225 | return true;
226 | }
227 |
228 | @Override
229 | public void onPause() {
230 | super.onPause();
231 |
232 | int pos = listView.getCheckedItemPosition();
233 | if (pos >= 0 && pos < listItems.size()) {
234 | String profile = listItems.get(pos);
235 | PrefStore.changeProfile(this, profile);
236 | }
237 | }
238 |
239 | @Override
240 | public void onResume() {
241 | super.onResume();
242 | setTitle(R.string.title_activity_profiles);
243 | listItems.clear();
244 | listItems.addAll(getProfiles(this));
245 | Collections.sort(listItems);
246 | String profile = PrefStore.getProfileName(this);
247 | if (listItems.size() == 0) listItems.add(profile);
248 | adapter.notifyDataSetChanged();
249 | listView.setItemChecked(getPosition(profile), true);
250 | }
251 |
252 | @Override
253 | public boolean onTouch(View v, MotionEvent event) {
254 | gd.onTouchEvent(event);
255 | return false;
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/activity/PropertiesActivity.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.activity;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.annotation.Nullable;
6 | import androidx.appcompat.app.AppCompatActivity;
7 |
8 | import ru.meefik.linuxdeploy.PrefStore;
9 | import ru.meefik.linuxdeploy.R;
10 | import ru.meefik.linuxdeploy.fragment.PropertiesFragment;
11 |
12 | public class PropertiesActivity extends AppCompatActivity {
13 |
14 | @Override
15 | protected void onCreate(@Nullable Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | PrefStore.setLocale(this);
18 | setContentView(R.layout.activity_preference);
19 |
20 | getSupportFragmentManager()
21 | .beginTransaction()
22 | .replace(R.id.frame_layout, new PropertiesFragment())
23 | .commit();
24 |
25 | // Restore from conf file if open from main activity
26 | if (getIntent().getBooleanExtra("restore", false)) {
27 | PrefStore.restoreProperties(this);
28 | }
29 | }
30 |
31 | @Override
32 | public void setTheme(int resId) {
33 | super.setTheme(PrefStore.getTheme(this));
34 | }
35 |
36 | @Override
37 | protected void onResume() {
38 | super.onResume();
39 |
40 | String titleMsg = getString(R.string.title_activity_properties)
41 | + ": " + PrefStore.getProfileName(this);
42 | setTitle(titleMsg);
43 | }
44 |
45 | @Override
46 | protected void onPause() {
47 | super.onPause();
48 |
49 | // Update configuration file
50 | PrefStore.dumpProperties(this);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/activity/RepositoryActivity.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.activity;
2 |
3 | import android.app.ProgressDialog;
4 | import android.content.Intent;
5 | import android.content.pm.PackageManager;
6 | import android.net.Uri;
7 | import android.os.Bundle;
8 | import android.view.LayoutInflater;
9 | import android.view.Menu;
10 | import android.view.MenuItem;
11 | import android.view.View;
12 | import android.widget.EditText;
13 | import android.widget.Toast;
14 |
15 | import androidx.appcompat.app.AlertDialog;
16 | import androidx.appcompat.app.AppCompatActivity;
17 | import androidx.recyclerview.widget.DividerItemDecoration;
18 | import androidx.recyclerview.widget.LinearLayoutManager;
19 | import androidx.recyclerview.widget.RecyclerView;
20 |
21 | import org.jetbrains.annotations.NotNull;
22 |
23 | import java.io.BufferedReader;
24 | import java.io.FileOutputStream;
25 | import java.io.IOException;
26 | import java.io.InputStreamReader;
27 | import java.io.OutputStream;
28 | import java.util.ArrayList;
29 | import java.util.List;
30 | import java.util.zip.GZIPInputStream;
31 |
32 | import okhttp3.Call;
33 | import okhttp3.Callback;
34 | import okhttp3.OkHttpClient;
35 | import okhttp3.Request;
36 | import okhttp3.Response;
37 | import ru.meefik.linuxdeploy.PrefStore;
38 | import ru.meefik.linuxdeploy.R;
39 | import ru.meefik.linuxdeploy.adapter.RepositoryProfileAdapter;
40 | import ru.meefik.linuxdeploy.model.RepositoryProfile;
41 |
42 | public class RepositoryActivity extends AppCompatActivity {
43 |
44 | private RepositoryProfileAdapter adapter;
45 |
46 | private boolean isDonated() {
47 | return getPackageManager().checkSignatures(getPackageName(), "ru.meefik.donate")
48 | == PackageManager.SIGNATURE_MATCH;
49 | }
50 |
51 | private void importDialog(final RepositoryProfile repositoryProfile) {
52 | final String name = repositoryProfile.getProfile();
53 | final String message = getString(R.string.repository_import_message,
54 | repositoryProfile.getDescription(),
55 | repositoryProfile.getSize());
56 |
57 | AlertDialog.Builder dialog = new AlertDialog.Builder(this)
58 | .setTitle(name)
59 | .setMessage(message)
60 | .setCancelable(false)
61 | .setNegativeButton(android.R.string.no, (dialog13, which) -> dialog13.cancel());
62 |
63 | if (isDonated()) {
64 | dialog.setPositiveButton(R.string.repository_import_button,
65 | (dialog1, whichButton) -> importProfile(name));
66 | } else {
67 | dialog.setPositiveButton(R.string.repository_purchase_button,
68 | (dialog12, whichButton) -> startActivity(new Intent(Intent.ACTION_VIEW,
69 | Uri.parse("https://play.google.com/store/apps/details?id=ru.meefik.donate"))));
70 | }
71 |
72 | dialog.show();
73 | }
74 |
75 | private void changeUrlDialog() {
76 | View view = LayoutInflater.from(this).inflate(R.layout.edit_text_dialog, null);
77 | EditText input = view.findViewById(R.id.edit_text);
78 | input.setText(PrefStore.getRepositoryUrl(this));
79 | input.setSelection(input.getText().length());
80 |
81 | new AlertDialog.Builder(this)
82 | .setTitle(R.string.repository_change_url_title)
83 | .setView(view)
84 | .setPositiveButton(android.R.string.ok,
85 | (dialog, whichButton) -> {
86 | String text = input.getText().toString();
87 | if (text.isEmpty())
88 | text = getString(R.string.repository_url);
89 | PrefStore.setRepositoryUrl(getApplicationContext(), text);
90 | retrieveIndex();
91 | })
92 | .setNegativeButton(android.R.string.cancel,
93 | (dialog, whichButton) -> dialog.cancel())
94 | .show();
95 | }
96 |
97 | private void retrieveIndex() {
98 | String url = PrefStore.getRepositoryUrl(this);
99 |
100 | OkHttpClient client = new OkHttpClient.Builder()
101 | .followRedirects(true)
102 | .build();
103 | Request request = new Request.Builder()
104 | .url(url + "/index.gz")
105 | .build();
106 |
107 | ProgressDialog dialog = new ProgressDialog(this);
108 | dialog.setMessage(getString(R.string.loading_message));
109 | dialog.setCancelable(false);
110 | dialog.show();
111 |
112 | client.newCall(request).enqueue(new Callback() {
113 | @Override
114 | public void onFailure(@NotNull Call call, @NotNull IOException e) {
115 | onFailure();
116 | }
117 |
118 | @Override
119 | public void onResponse(@NotNull Call call, @NotNull Response response) {
120 | if (response.isSuccessful()) {
121 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(response.body().byteStream())))) {
122 | List repositoryProfiles = new ArrayList<>();
123 | String line;
124 | RepositoryProfile repositoryProfile = null;
125 | while ((line = reader.readLine()) != null) {
126 | if (line.startsWith("PROFILE")) {
127 | repositoryProfile = new RepositoryProfile();
128 | repositoryProfile.setProfile(line.split("=")[1]);
129 | } else if (line.startsWith("DESC")) {
130 | repositoryProfile.setDescription(line.split("=")[1]);
131 | } else if (line.startsWith("TYPE")) {
132 | repositoryProfile.setType(line.split("=")[1]);
133 | } else if (line.startsWith("SIZE")) {
134 | repositoryProfile.setSize(line.split("=")[1]);
135 | repositoryProfiles.add(repositoryProfile);
136 | }
137 | }
138 |
139 | runOnUiThread(() -> {
140 | adapter.setRepositoryProfiles(repositoryProfiles);
141 | dialog.dismiss();
142 | });
143 | } catch (IOException e) {
144 | onFailure();
145 | }
146 | } else {
147 | onFailure();
148 | }
149 | }
150 |
151 | private void onFailure() {
152 | runOnUiThread(() -> {
153 | dialog.dismiss();
154 | Toast.makeText(RepositoryActivity.this, R.string.toast_loading_error, Toast.LENGTH_SHORT).show();
155 | });
156 | }
157 | });
158 | }
159 |
160 | private void importProfile(String name) {
161 | String url = PrefStore.getRepositoryUrl(this);
162 |
163 | OkHttpClient client = new OkHttpClient.Builder()
164 | .followRedirects(true)
165 | .build();
166 | Request request = new Request.Builder()
167 | .url(url + "/index.gz")
168 | .build();
169 |
170 | ProgressDialog dialog = new ProgressDialog(this);
171 | dialog.setMessage(getString(R.string.loading_message));
172 | dialog.setCancelable(false);
173 | dialog.show();
174 |
175 | client.newCall(request).enqueue(new Callback() {
176 | @Override
177 | public void onFailure(@NotNull Call call, @NotNull IOException e) {
178 | onFailure();
179 | }
180 |
181 | @Override
182 | public void onResponse(@NotNull Call call, @NotNull Response response) {
183 | if (response.isSuccessful()) {
184 | String conf = PrefStore.getEnvDir(RepositoryActivity.this) + "/config/" + name + ".conf";
185 | try (OutputStream os = new FileOutputStream(conf)) {
186 | os.write(response.body().bytes());
187 |
188 | runOnUiThread(dialog::dismiss);
189 | PrefStore.changeProfile(RepositoryActivity.this, name);
190 | finish();
191 | } catch (IOException e) {
192 | onFailure();
193 | }
194 | } else {
195 | onFailure();
196 | }
197 | }
198 |
199 | private void onFailure() {
200 | runOnUiThread(() -> {
201 | dialog.dismiss();
202 | Toast.makeText(RepositoryActivity.this, R.string.toast_loading_error, Toast.LENGTH_SHORT).show();
203 | });
204 | }
205 | });
206 | }
207 |
208 | @Override
209 | protected void onCreate(Bundle savedInstanceState) {
210 | super.onCreate(savedInstanceState);
211 | PrefStore.setLocale(this);
212 | setContentView(R.layout.activity_repository);
213 |
214 | // RecyclerView Adapter
215 | RecyclerView recyclerView = findViewById(R.id.repositoryView);
216 | adapter = new RepositoryProfileAdapter();
217 | adapter.setOnItemClickListener(this::importDialog);
218 | recyclerView.setAdapter(adapter);
219 | recyclerView.setLayoutManager(new LinearLayoutManager(this));
220 | recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
221 |
222 | // Load list
223 | retrieveIndex();
224 | }
225 |
226 | @Override
227 | public void setTheme(int resId) {
228 | super.setTheme(PrefStore.getTheme(this));
229 | }
230 |
231 | @Override
232 | public void onResume() {
233 | super.onResume();
234 | setTitle(R.string.title_activity_repository);
235 | }
236 |
237 | @Override
238 | public boolean onCreateOptionsMenu(Menu menu) {
239 | PrefStore.setLocale(this);
240 | getMenuInflater().inflate(R.menu.activity_repository, menu);
241 | return super.onCreateOptionsMenu(menu);
242 | }
243 |
244 | @Override
245 | public boolean onOptionsItemSelected(MenuItem item) {
246 | switch (item.getItemId()) {
247 | case R.id.menu_refresh:
248 | retrieveIndex();
249 | break;
250 | case R.id.menu_change_url:
251 | changeUrlDialog();
252 | break;
253 | default:
254 | return super.onOptionsItemSelected(item);
255 | }
256 |
257 | return true;
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/activity/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.activity;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.appcompat.app.AppCompatActivity;
6 |
7 | import ru.meefik.linuxdeploy.PrefStore;
8 | import ru.meefik.linuxdeploy.R;
9 | import ru.meefik.linuxdeploy.fragment.SettingsFragment;
10 |
11 | public class SettingsActivity extends AppCompatActivity {
12 |
13 | @Override
14 | protected void onCreate(Bundle savedInstanceState) {
15 | super.onCreate(savedInstanceState);
16 | PrefStore.setLocale(this);
17 | setContentView(R.layout.activity_preference);
18 |
19 | getSupportFragmentManager()
20 | .beginTransaction()
21 | .replace(R.id.frame_layout, new SettingsFragment())
22 | .commit();
23 |
24 | // Restore from conf file
25 | PrefStore.restoreSettings(this);
26 | }
27 |
28 | @Override
29 | public void setTheme(int resId) {
30 | super.setTheme(PrefStore.getTheme(this));
31 | }
32 |
33 | @Override
34 | public void onResume() {
35 | super.onResume();
36 |
37 | setTitle(R.string.title_activity_settings);
38 | }
39 |
40 | @Override
41 | public void onPause() {
42 | super.onPause();
43 |
44 | // update configuration file
45 | PrefStore.dumpSettings(this);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/adapter/MountAdapter.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.adapter;
2 |
3 | import android.view.LayoutInflater;
4 | import android.view.View;
5 | import android.view.ViewGroup;
6 | import android.widget.ImageView;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.recyclerview.widget.RecyclerView;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | import ru.meefik.linuxdeploy.R;
16 | import ru.meefik.linuxdeploy.model.Mount;
17 |
18 | public class MountAdapter extends RecyclerView.Adapter {
19 |
20 | private List mounts;
21 | private OnItemClickListener clickListener;
22 | private OnItemDeleteListener deleteListener;
23 |
24 | public MountAdapter() {
25 | this.mounts = new ArrayList<>();
26 | }
27 |
28 | @NonNull
29 | @Override
30 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
31 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.mounts_row, parent, false);
32 | return new ViewHolder(view);
33 | }
34 |
35 | @Override
36 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
37 | holder.setMount(mounts.get(position));
38 | }
39 |
40 | @Override
41 | public int getItemCount() {
42 | return mounts == null ? 0 : mounts.size();
43 | }
44 |
45 | public void addMount(Mount mount) {
46 | mounts.add(mount);
47 | notifyDataSetChanged();
48 | }
49 |
50 | public void removeMount(Mount mount) {
51 | mounts.remove(mount);
52 | notifyDataSetChanged();
53 | }
54 |
55 | public void setMounts(List mounts) {
56 | this.mounts.clear();
57 | for (String mount : mounts) {
58 | String[] tmp = mount.split(":", 2);
59 | if (tmp.length > 1) {
60 | this.mounts.add(new Mount(tmp[0], tmp[1]));
61 | } else {
62 | this.mounts.add(new Mount(tmp[0], ""));
63 | }
64 | }
65 | notifyDataSetChanged();
66 | }
67 |
68 | public List getMounts() {
69 | List mounts = new ArrayList<>();
70 | for (Mount mount : this.mounts) {
71 | if (mount.getTarget().isEmpty()) {
72 | mounts.add(mount.getSource());
73 | } else {
74 | mounts.add(mount.getSource() + ":" + mount.getTarget());
75 | }
76 | }
77 | return mounts;
78 | }
79 |
80 | public void setOnItemClickListener(OnItemClickListener clickListener) {
81 | this.clickListener = clickListener;
82 | }
83 |
84 | public void setOnItemDeleteListener(OnItemDeleteListener deleteListener) {
85 | this.deleteListener = deleteListener;
86 | }
87 |
88 | public interface OnItemClickListener {
89 | void onItemClick(Mount mount);
90 | }
91 |
92 | public interface OnItemDeleteListener {
93 | void onItemDelete(Mount mount);
94 | }
95 |
96 | class ViewHolder extends RecyclerView.ViewHolder {
97 |
98 | private View view;
99 | private TextView mountPoint;
100 | private ImageView delete;
101 |
102 | ViewHolder(@NonNull View itemView) {
103 | super(itemView);
104 |
105 | view = itemView;
106 | mountPoint = itemView.findViewById(R.id.mount_point);
107 | delete = itemView.findViewById(R.id.delete_mount);
108 | }
109 |
110 | void setMount(Mount mount) {
111 | if (mount.getTarget().isEmpty()) {
112 | mountPoint.setText(mount.getSource());
113 | } else {
114 | mountPoint.setText(mount.getSource() + " - " + mount.getTarget());
115 | }
116 |
117 | view.setOnClickListener(v -> {
118 | if (clickListener != null)
119 | clickListener.onItemClick(mount);
120 | });
121 |
122 | delete.setOnClickListener(v -> {
123 | if (deleteListener != null)
124 | deleteListener.onItemDelete(mount);
125 | });
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/adapter/RepositoryProfileAdapter.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.adapter;
2 |
3 | import android.view.LayoutInflater;
4 | import android.view.View;
5 | import android.view.ViewGroup;
6 | import android.widget.ImageView;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.recyclerview.widget.RecyclerView;
11 |
12 | import java.util.List;
13 |
14 | import ru.meefik.linuxdeploy.R;
15 | import ru.meefik.linuxdeploy.model.RepositoryProfile;
16 |
17 | public class RepositoryProfileAdapter extends RecyclerView.Adapter {
18 |
19 | private List repositoryProfiles;
20 | private OnItemClickListener listener;
21 |
22 | @NonNull
23 | @Override
24 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
25 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.repository_row, parent, false);
26 | return new ViewHolder(view);
27 | }
28 |
29 | @Override
30 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
31 | holder.setRepository(repositoryProfiles.get(position));
32 | }
33 |
34 | @Override
35 | public int getItemCount() {
36 | return repositoryProfiles != null ? repositoryProfiles.size() : 0;
37 | }
38 |
39 | public void setRepositoryProfiles(List repositoryProfiles) {
40 | this.repositoryProfiles = repositoryProfiles;
41 | notifyDataSetChanged();
42 | }
43 |
44 | public void setOnItemClickListener(OnItemClickListener listener) {
45 | this.listener = listener;
46 | }
47 |
48 | class ViewHolder extends RecyclerView.ViewHolder {
49 |
50 | private View view;
51 | private TextView title;
52 | private TextView subTitle;
53 | private ImageView icon;
54 |
55 | ViewHolder(@NonNull View itemView) {
56 | super(itemView);
57 |
58 | view = itemView;
59 | title = itemView.findViewById(R.id.repo_entry_title);
60 | subTitle = itemView.findViewById(R.id.repo_entry_subtitle);
61 | icon = itemView.findViewById(R.id.repo_entry_icon);
62 | }
63 |
64 | public void setRepository(RepositoryProfile repositoryProfile) {
65 | int iconRes = R.raw.linux;
66 | if (repositoryProfile.getType() != null) {
67 | switch (repositoryProfile.getType()) {
68 | case "alpine":
69 | iconRes = R.raw.alpine;
70 | break;
71 | case "archlinux":
72 | iconRes = R.raw.archlinux;
73 | break;
74 | case "centos":
75 | iconRes = R.raw.centos;
76 | break;
77 | case "debian":
78 | iconRes = R.raw.debian;
79 | break;
80 | case "fedora":
81 | iconRes = R.raw.fedora;
82 | break;
83 | case "kali":
84 | iconRes = R.raw.kali;
85 | break;
86 | case "slackware":
87 | iconRes = R.raw.slackware;
88 | break;
89 | case "ubuntu":
90 | iconRes = R.raw.ubuntu;
91 | break;
92 | }
93 | }
94 |
95 | icon.setImageResource(iconRes);
96 | title.setText(repositoryProfile.getProfile());
97 | if (repositoryProfile.getDescription() != null && !repositoryProfile.getDescription().isEmpty())
98 | subTitle.setText(repositoryProfile.getDescription());
99 | else
100 | subTitle.setText(view.getContext().getString(R.string.repository_default_description));
101 |
102 | view.setOnClickListener(v -> {
103 | if (listener != null)
104 | listener.onClick(repositoryProfile);
105 | });
106 | }
107 | }
108 |
109 | public interface OnItemClickListener {
110 | void onClick(RepositoryProfile repositoryProfile);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/fragment/SettingsFragment.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.fragment;
2 |
3 | import android.Manifest;
4 | import android.content.ComponentName;
5 | import android.content.Context;
6 | import android.content.SharedPreferences;
7 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
8 | import android.content.pm.PackageManager;
9 | import android.os.Bundle;
10 |
11 | import androidx.appcompat.app.AlertDialog;
12 | import androidx.core.app.ActivityCompat;
13 | import androidx.core.content.ContextCompat;
14 | import androidx.preference.CheckBoxPreference;
15 | import androidx.preference.EditTextPreference;
16 | import androidx.preference.ListPreference;
17 | import androidx.preference.Preference;
18 | import androidx.preference.PreferenceFragmentCompat;
19 | import androidx.preference.PreferenceGroup;
20 | import androidx.preference.PreferenceScreen;
21 |
22 | import ru.meefik.linuxdeploy.EnvUtils;
23 | import ru.meefik.linuxdeploy.PrefStore;
24 | import ru.meefik.linuxdeploy.R;
25 | import ru.meefik.linuxdeploy.RemoveEnvTask;
26 | import ru.meefik.linuxdeploy.UpdateEnvTask;
27 | import ru.meefik.linuxdeploy.receiver.BootReceiver;
28 |
29 | public class SettingsFragment extends PreferenceFragmentCompat implements
30 | OnSharedPreferenceChangeListener, Preference.OnPreferenceClickListener {
31 |
32 | @Override
33 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
34 | getPreferenceManager().setSharedPreferencesName(PrefStore.getSettingsSharedName());
35 | setPreferencesFromResource(R.xml.settings, rootKey);
36 | initSummaries(getPreferenceScreen());
37 | }
38 |
39 | @Override
40 | public void onResume() {
41 | super.onResume();
42 |
43 | getPreferenceScreen().getSharedPreferences()
44 | .registerOnSharedPreferenceChangeListener(this);
45 | }
46 |
47 | @Override
48 | public void onPause() {
49 | super.onPause();
50 |
51 | getPreferenceScreen().getSharedPreferences()
52 | .unregisterOnSharedPreferenceChangeListener(this);
53 | }
54 |
55 | @Override
56 | public boolean onPreferenceClick(Preference preference) {
57 | switch (preference.getKey()) {
58 | case "installenv":
59 | updateEnvDialog();
60 | return true;
61 | case "removeenv":
62 | removeEnvDialog();
63 | return true;
64 | default:
65 | return false;
66 | }
67 | }
68 |
69 | @Override
70 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
71 | Preference pref = findPreference(key);
72 | setSummary(pref, true);
73 | switch (key) {
74 | case "is_telnet":
75 | // start/stop telnetd
76 | EnvUtils.execService(getContext(), "telnetd", null);
77 | break;
78 | case "telnet_port":
79 | // restart telnetd
80 | EnvUtils.execService(getContext(), "telnetd", "restart");
81 | // restart httpd
82 | EnvUtils.execService(getContext(), "httpd", "restart");
83 | break;
84 | case "telnet_localhost":
85 | // restart telnetd
86 | EnvUtils.execService(getContext(), "telnetd", "restart");
87 | break;
88 | case "is_http":
89 | // start/stop httpd
90 | EnvUtils.execService(getContext(), "httpd", null);
91 | break;
92 | case "http_port":
93 | case "http_conf":
94 | // restart httpd
95 | EnvUtils.execService(getContext(), "httpd", "restart");
96 | break;
97 | case "autostart":
98 | // set autostart settings
99 | int autostartFlag = (PrefStore.isAutostart(getContext()) ?
100 | PackageManager.COMPONENT_ENABLED_STATE_ENABLED
101 | : PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
102 | ComponentName bootComponent = new ComponentName(getContext(), BootReceiver.class);
103 | getContext().getPackageManager().setComponentEnabledSetting(bootComponent, autostartFlag,
104 | PackageManager.DONT_KILL_APP);
105 | break;
106 | case "stealth":
107 | // set stealth mode
108 | // Run app without launcher: am start -n ru.meefik.linuxdeploy/.MainActivity
109 | int stealthFlag = PrefStore.isStealth(getContext()) ?
110 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED
111 | : PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
112 | ComponentName mainComponent = new ComponentName(getContext().getPackageName(), getContext().getPackageName() + ".Launcher");
113 | getContext().getPackageManager().setComponentEnabledSetting(mainComponent, stealthFlag,
114 | PackageManager.DONT_KILL_APP);
115 | break;
116 | }
117 | }
118 |
119 | private void initSummaries(PreferenceGroup pg) {
120 | for (int i = 0; i < pg.getPreferenceCount(); ++i) {
121 | Preference p = pg.getPreference(i);
122 | if (p instanceof PreferenceGroup)
123 | initSummaries((PreferenceGroup) p);
124 | else
125 | setSummary(p, false);
126 | if (p instanceof PreferenceScreen)
127 | p.setOnPreferenceClickListener(this);
128 | }
129 | }
130 |
131 | private void setSummary(Preference pref, boolean init) {
132 | if (pref instanceof EditTextPreference) {
133 | EditTextPreference editPref = (EditTextPreference) pref;
134 | pref.setSummary(editPref.getText());
135 |
136 | switch (editPref.getKey()) {
137 | case "env_dir":
138 | if (!init) {
139 | editPref.setText(PrefStore.getEnvDir(getContext()));
140 | pref.setSummary(editPref.getText());
141 | }
142 | break;
143 | case "http_conf":
144 | if (editPref.getText().isEmpty()) {
145 | editPref.setText(PrefStore.getHttpConf(getContext()));
146 | pref.setSummary(editPref.getText());
147 | }
148 | break;
149 | case "logfile":
150 | if (!init) {
151 | editPref.setText(PrefStore.getLogFile(getContext()));
152 | pref.setSummary(editPref.getText());
153 | }
154 | break;
155 | }
156 | }
157 |
158 | if (pref instanceof ListPreference) {
159 | ListPreference listPref = (ListPreference) pref;
160 | pref.setSummary(listPref.getEntry());
161 | }
162 |
163 | if (pref instanceof CheckBoxPreference) {
164 | CheckBoxPreference checkPref = (CheckBoxPreference) pref;
165 |
166 | if (checkPref.getKey().equals("logger") && checkPref.isChecked() && init) {
167 | requestWritePermissions();
168 | }
169 | }
170 | }
171 |
172 | private void updateEnvDialog() {
173 | final Context context = getContext();
174 | new AlertDialog.Builder(getContext())
175 | .setTitle(R.string.title_installenv_preference)
176 | .setMessage(R.string.message_installenv_confirm_dialog)
177 | .setIcon(android.R.drawable.ic_dialog_alert)
178 | .setCancelable(false)
179 | .setPositiveButton(android.R.string.yes,
180 | (dialog, id) -> new UpdateEnvTask(context).execute())
181 | .setNegativeButton(android.R.string.no,
182 | (dialog, id) -> dialog.cancel()).show();
183 | }
184 |
185 | private void removeEnvDialog() {
186 | final Context context = getContext();
187 | new AlertDialog.Builder(getContext())
188 | .setTitle(R.string.title_removeenv_preference)
189 | .setMessage(R.string.message_removeenv_confirm_dialog)
190 | .setIcon(android.R.drawable.ic_dialog_alert)
191 | .setCancelable(false)
192 | .setPositiveButton(android.R.string.yes,
193 | (dialog, id) -> new RemoveEnvTask(context).execute())
194 | .setNegativeButton(android.R.string.no,
195 | (dialog, id) -> dialog.cancel()).show();
196 | }
197 |
198 | /**
199 | * Request permission for write to storage
200 | */
201 | private void requestWritePermissions() {
202 | int REQUEST_WRITE_STORAGE = 112;
203 | boolean hasPermission = (ContextCompat.checkSelfPermission(getContext(),
204 | Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
205 | if (!hasPermission) {
206 | ActivityCompat.requestPermissions(getActivity(),
207 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_STORAGE);
208 | }
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/model/Mount.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.model;
2 |
3 | public class Mount {
4 | private String source;
5 | private String target;
6 |
7 | public Mount() {
8 | }
9 |
10 | public Mount(String source, String target) {
11 | this.source = source;
12 | this.target = target;
13 | }
14 |
15 | public String getSource() {
16 | return source;
17 | }
18 |
19 | public void setSource(String source) {
20 | this.source = source;
21 | }
22 |
23 | public String getTarget() {
24 | return target;
25 | }
26 |
27 | public void setTarget(String target) {
28 | this.target = target;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/model/RepositoryProfile.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.model;
2 |
3 | public class RepositoryProfile {
4 | private String profile;
5 | private String description;
6 | private String type;
7 | private String size;
8 |
9 | public RepositoryProfile() {
10 | // Empty constructor
11 | }
12 |
13 | public String getProfile() {
14 | return profile;
15 | }
16 |
17 | public void setProfile(String profile) {
18 | this.profile = profile;
19 | }
20 |
21 | public String getDescription() {
22 | return description;
23 | }
24 |
25 | public void setDescription(String description) {
26 | this.description = description;
27 | }
28 |
29 | public String getType() {
30 | return type;
31 | }
32 |
33 | public void setType(String type) {
34 | this.type = type;
35 | }
36 |
37 | public String getSize() {
38 | return size;
39 | }
40 |
41 | public void setSize(String size) {
42 | this.size = size;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/receiver/ActionReceiver.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.receiver;
2 |
3 | import android.app.NotificationManager;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 |
8 | import androidx.core.app.NotificationCompat;
9 |
10 | import ru.meefik.linuxdeploy.EnvUtils;
11 | import ru.meefik.linuxdeploy.R;
12 | import ru.meefik.linuxdeploy.activity.MainActivity;
13 |
14 | import static ru.meefik.linuxdeploy.App.SERVICE_CHANNEL_ID;
15 |
16 | public class ActionReceiver extends BroadcastReceiver {
17 |
18 | final static int NOTIFY_ID = 2;
19 | static long attemptTime = 0;
20 | static long attemptNumber = 1;
21 |
22 | private void showNotification(Context c, int icon, String text) {
23 | NotificationManager mNotificationManager = (NotificationManager) c
24 | .getSystemService(Context.NOTIFICATION_SERVICE);
25 | NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(c, SERVICE_CHANNEL_ID)
26 | .setSmallIcon(icon)
27 | .setContentTitle(c.getString(R.string.app_name))
28 | .setContentText(text);
29 | mBuilder.setWhen(0);
30 | mNotificationManager.notify(NOTIFY_ID, mBuilder.build());
31 | }
32 |
33 | private void hideNotification(Context c) {
34 | NotificationManager mNotificationManager = (NotificationManager) c
35 | .getSystemService(Context.NOTIFICATION_SERVICE);
36 | mNotificationManager.cancel(NOTIFY_ID);
37 | }
38 |
39 | @Override
40 | public void onReceive(final Context context, Intent intent) {
41 | // am broadcast -a ru.meefik.linuxdeploy.BROADCAST_ACTION --user 0 --esn "hide"
42 | if (intent.hasExtra("hide")) {
43 | hideNotification(context);
44 | return;
45 | }
46 | // am broadcast -a ru.meefik.linuxdeploy.BROADCAST_ACTION --user 0 --es "info" "Hello World!"
47 | if (intent.hasExtra("info")) {
48 | showNotification(context, android.R.drawable.ic_dialog_info, intent.getStringExtra("info"));
49 | return;
50 | }
51 | // am broadcast -a ru.meefik.linuxdeploy.BROADCAST_ACTION --user 0 --es "alert" "Hello World!"
52 | if (intent.hasExtra("alert")) {
53 | showNotification(context, android.R.drawable.ic_dialog_alert, intent.getStringExtra("alert"));
54 | return;
55 | }
56 | // am broadcast -a ru.meefik.linuxdeploy.BROADCAST_ACTION --user 0 --esn "start"
57 | if (intent.hasExtra("start")) {
58 | System.out.println("START");
59 | EnvUtils.execService(context, "start", "-m");
60 | return;
61 | }
62 | // am broadcast -a ru.meefik.linuxdeploy.BROADCAST_ACTION --user 0 --esn "stop"
63 | if (intent.hasExtra("stop")) {
64 | System.out.println("STOP");
65 | EnvUtils.execService(context, "stop", "-u");
66 | return;
67 | }
68 | // am broadcast -a ru.meefik.linuxdeploy.BROADCAST_ACTION --user 0 --esn "show"
69 | if (intent.hasExtra("show")) {
70 | if (attemptTime > System.currentTimeMillis() - 5000) {
71 | attemptNumber++;
72 | } else {
73 | attemptNumber = 1;
74 | }
75 | attemptTime = System.currentTimeMillis();
76 | if (attemptNumber >= 5) {
77 | attemptNumber = 1;
78 | Intent mainIntent = new Intent(context.getApplicationContext(), MainActivity.class);
79 | mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
80 | context.startActivity(mainIntent);
81 | }
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/receiver/BootReceiver.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.receiver;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 |
7 | import ru.meefik.linuxdeploy.EnvUtils;
8 | import ru.meefik.linuxdeploy.PrefStore;
9 |
10 | public class BootReceiver extends BroadcastReceiver {
11 |
12 | @Override
13 | public void onReceive(final Context context, Intent intent) {
14 | String action = intent.getAction();
15 | if (action == null) return;
16 | switch (action) {
17 | case Intent.ACTION_BOOT_COMPLETED:
18 | try { // Autostart delay
19 | Integer delay_s = PrefStore.getAutostartDelay(context);
20 | Thread.sleep(delay_s * 1000);
21 | } catch (InterruptedException e) {
22 | e.printStackTrace();
23 | }
24 | EnvUtils.execServices(context, new String[]{"telnetd", "httpd"}, "start");
25 | EnvUtils.execService(context, "start", "-m");
26 | break;
27 | case Intent.ACTION_SHUTDOWN:
28 | EnvUtils.execService(context, "stop", "-u");
29 | EnvUtils.execServices(context, new String[]{"telnetd", "httpd"}, "stop");
30 | try { // Shutdown delay
31 | Integer delay_s = PrefStore.getAutostartDelay(context);
32 | Thread.sleep(delay_s * 1000);
33 | } catch (InterruptedException e) {
34 | e.printStackTrace();
35 | }
36 | break;
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/receiver/NetworkReceiver.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.receiver;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.net.ConnectivityManager;
7 | import android.net.Network;
8 | import android.net.NetworkCapabilities;
9 | import android.net.NetworkInfo;
10 |
11 | import ru.meefik.linuxdeploy.EnvUtils;
12 |
13 | public class NetworkReceiver extends BroadcastReceiver {
14 |
15 | @Override
16 | public void onReceive(final Context context, Intent intent) {
17 | if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
18 | ConnectivityManager cm =
19 | (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
20 |
21 | boolean isConnected;
22 |
23 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
24 | Network activeNetwork = cm.getActiveNetwork();
25 | NetworkCapabilities networkCapabilities = cm.getNetworkCapabilities(activeNetwork);
26 |
27 | isConnected = networkCapabilities != null
28 | && (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
29 | || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
30 | || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET));
31 | } else {
32 | NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
33 | isConnected = activeNetworkInfo != null && activeNetworkInfo.isConnected();
34 | }
35 |
36 | if (isConnected) {
37 | EnvUtils.execService(context, "start", "core/net");
38 | } else {
39 | EnvUtils.execService(context, "stop", "core/net");
40 | }
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/meefik/linuxdeploy/receiver/PowerReceiver.java:
--------------------------------------------------------------------------------
1 | package ru.meefik.linuxdeploy.receiver;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 |
7 | import ru.meefik.linuxdeploy.EnvUtils;
8 |
9 | public class PowerReceiver extends BroadcastReceiver {
10 |
11 | @Override
12 | public void onReceive(final Context context, Intent intent) {
13 | if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
14 | EnvUtils.execService(context, "stop", "core/power");
15 | } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
16 | EnvUtils.execService(context, "start", "core/power");
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_computer_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_edit_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_exit_to_app_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_help_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play_arrow_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_refresh_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_stop_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_tune_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_warning_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/activity_about.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
14 |
15 |
22 |
23 |
30 |
31 |
39 |
40 |
47 |
48 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/content_main.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
21 |
22 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_about.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
16 |
17 |
24 |
25 |
34 |
35 |
42 |
43 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_fullscreen.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_mounts.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_preference.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_profiles.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_repository.xml:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
21 |
22 |
32 |
33 |
34 |
35 |
45 |
46 |
59 |
60 |
73 |
74 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/edit_text_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/mounts_row.xml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
23 |
24 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/properties_mounts.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/repository_row.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
21 |
22 |
39 |
40 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_main_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_main_landscape.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_main_portrait.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_mounts.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_profiles.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_repository.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/alpine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/raw/alpine.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/archlinux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/raw/archlinux.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/centos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/raw/centos.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/debian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/raw/debian.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/fedora.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/raw/fedora.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/kali.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/raw/kali.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/raw/linux.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/slackware.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/raw/slackware.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/tux_about.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/raw/tux_about.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/ubuntu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/app/src/main/res/raw/ubuntu.png
--------------------------------------------------------------------------------
/app/src/main/res/values-de/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Dunkel
5 | - Hell
6 |
7 |
8 | - Datei
9 | - Verzeichnis
10 | - Partition
11 | - RAM
12 | - Custom
13 |
14 |
15 | - XTerm
16 | - LXDE
17 | - Xfce
18 | - MATE
19 | - Andere
20 |
21 |
22 | - Nicht freeze
23 | - Pause
24 | - Stop
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-es/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Oscuro
5 | - Claro
6 |
7 |
8 | - Archivo
9 | - Directorio
10 | - Partición
11 | - RAM
12 | - Custom
13 |
14 |
15 | - XTerm
16 | - LXDE
17 | - Xfce
18 | - MATE
19 | - Otro
20 |
21 |
22 | - No congelar
23 | - Pausa
24 | - Parar
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-fr/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Sombre
5 | - Clair
6 |
7 |
8 | - Fichier
9 | - Répertoire
10 | - Partition
11 | - RAM
12 | - Custom
13 |
14 |
15 | - XTerm
16 | - LXDE
17 | - Xfce
18 | - MATE
19 | - Autre
20 |
21 |
22 | - Ne pas freezer
23 | - Pause
24 | - Stop
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-in/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Gelap
5 | - Terang
6 |
7 |
8 | - Berkas img
9 | - Folder
10 | - Partisi
11 | - RAM
12 | - Khusus
13 |
14 |
15 | - XTerm
16 | - LXDE
17 | - Xfce
18 | - MATE
19 | - Lainnya
20 |
21 |
22 | - Jangan dibekukan
23 | - Jeda
24 | - Berhenti
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-it/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Scuro
5 | - Chiaro
6 |
7 |
8 | - File
9 | - Direttorio
10 | - Partizione
11 | - RAM
12 | - Custom
13 |
14 |
15 | - XTerm
16 | - LXDE
17 | - Xfce
18 | - MATE
19 | - Altro
20 |
21 |
22 | - Non congelare
23 | - Pausa
24 | - Ferma
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ko/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - 어두운
5 | - 밝은
6 |
7 |
8 | - 파일
9 | - 폴더
10 | - 파티션
11 | - 메모리(RAM)
12 | - Custom
13 |
14 |
15 | - XTerm
16 | - LXDE
17 | - Xfce
18 | - MATE
19 | - 설치안함
20 |
21 |
22 | - 멈추지 않음
23 | - 일시정지
24 | - 정지
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-pl/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Ciemny
5 | - Jasny
6 |
7 |
8 | - Plik
9 | - Katalog
10 | - Partycja
11 | - RAM
12 | - Custom
13 |
14 |
15 | - XTerm
16 | - LXDE
17 | - Xfce
18 | - MATE
19 | - Inny
20 |
21 |
22 | - Nie zatrzymuj
23 | - Wstrzymaj
24 | - Zatrzymaj
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-pt/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Escuro
5 | - Claro
6 |
7 |
8 | - Arquivo
9 | - Diretório
10 | - Partição
11 | - RAM
12 | - Custom
13 |
14 |
15 | - XTerm
16 | - LXDE
17 | - Xfce
18 | - MATE
19 | - Outro
20 |
21 |
22 | - Não congelar
23 | - Pausar
24 | - Parar
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ru/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Темная
5 | - Светлая
6 |
7 |
8 | - Файл
9 | - Директория
10 | - Раздел
11 | - Оперативная память
12 | - Пользовательский
13 |
14 |
15 | - XTerm
16 | - LXDE
17 | - Xfce
18 | - MATE
19 | - Другое
20 |
21 |
22 | - Не замораживать
23 | - Приостановить
24 | - Остановить
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sk/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Tmavá
5 | - Svetlá
6 |
7 |
8 | - Do súboru
9 | - Adresár
10 | - Na oddiel
11 | - Do pamäte RAM
12 | - Custom
13 |
14 |
15 | - XTerm
16 | - LXDE
17 | - Xfce
18 | - MATE
19 | - Iné
20 |
21 |
22 | - Nezmraziť
23 | - Pozastaviť
24 | - Zastaviť
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-vi/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Tối
5 | - Sáng
6 |
7 |
8 | - Tệp tin
9 | - Thư mục
10 | - Phân vùng
11 | - RAM
12 | - Custom
13 |
14 |
15 | - XTerm
16 | - LXDE
17 | - Xfce
18 | - MATE
19 | - Khác
20 |
21 |
22 | - Không đóng băng
23 | - Tạm dừng
24 | - Dừng lại
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - 暗色主题
5 | - 亮色主题
6 |
7 |
8 | - 镜像文件
9 | - 目录
10 | - 分区
11 | - RAM
12 | - 自定义
13 |
14 |
15 | - XTerm
16 | - LXDE
17 | - Xfce
18 | - MATE
19 | - 其它
20 |
21 |
22 | - 不冻结
23 | - 暂停
24 | - 停止
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 | #333
3 | #CCC
4 | #3F51B5
5 | #303F9F
6 | #FF4081
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/properties.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
22 |
23 |
31 |
32 |
38 |
39 |
47 |
48 |
54 |
55 |
62 |
63 |
71 |
72 |
78 |
79 |
86 |
87 |
93 |
94 |
102 |
103 |
109 |
110 |
116 |
117 |
123 |
124 |
125 |
126 |
127 |
128 |
133 |
134 |
143 |
144 |
149 |
150 |
151 |
152 |
153 |
154 |
159 |
160 |
165 |
166 |
167 |
168 |
169 |
170 |
175 |
176 |
181 |
182 |
183 |
184 |
185 |
186 |
191 |
192 |
197 |
198 |
199 |
200 |
201 |
202 |
207 |
208 |
217 |
218 |
223 |
224 |
233 |
234 |
235 |
236 |
237 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/properties_fb.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
18 |
24 |
30 |
31 |
36 |
37 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/properties_pulse.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/properties_run_parts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
17 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/properties_ssh.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/properties_sysv.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
18 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/properties_vnc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
22 |
23 |
30 |
37 |
44 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/properties_x11.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
18 |
23 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
15 |
20 |
21 |
29 |
30 |
37 |
38 |
45 |
46 |
54 |
55 |
60 |
65 |
70 |
75 |
83 |
88 |
93 |
94 |
95 |
101 |
107 |
111 |
115 |
116 |
117 |
122 |
130 |
136 |
141 |
149 |
156 |
157 |
158 |
163 |
168 |
173 |
174 |
181 |
182 |
183 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.6.1'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | google()
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/contrib/README.md:
--------------------------------------------------------------------------------
1 | Sources
2 | =======
3 |
4 | BusyBox: https://github.com/meefik/busybox
5 |
6 | PRoot: https://github.com/meefik/PRoot
7 |
8 | websocket.sh: https://github.com/meefik/websocket.sh
9 |
10 | pkgdetails https://dev.openwrt.org/browser/packages/admin/debootstrap/files/pkgdetails.c
11 |
12 | QEMU: https://packages.debian.org/stretch/qemu-user-static
13 |
14 | mke2fs (e2fsprogs): http://packages.debian.org/wheezy/e2fsprogs
15 |
16 |
--------------------------------------------------------------------------------
/contrib/dd/README.md:
--------------------------------------------------------------------------------
1 | dd Build Guide
2 | ==============
3 |
4 | dd - convert and copy a file.
5 |
6 | #### Build instruction
7 |
8 | 1) Prepare chroot environment (QEMU, Debian 7.0, armel).
9 |
10 | 2) Get coreutils (includes dd):
11 | ```
12 | apt-get build-dep coreutils
13 | apt-get source coreutils
14 | ```
15 |
16 | 3) Build coreutils:
17 | ```
18 | cd ./coreutils-8.13
19 | ./configure
20 | make CFLAGS=-static LDFLAGS=-static
21 | strip -s ./src/dd
22 | ```
23 |
24 | 4) Copy dd binary to assets directory.
25 |
26 |
--------------------------------------------------------------------------------
/contrib/e2fsprogs/README.md:
--------------------------------------------------------------------------------
1 | e2fsprogs Build Guide
2 | =====================
3 |
4 | e2fsprogs - ext2/ext3/ext4 file system utilities.
5 |
6 | #### Build instruction
7 |
8 | 1) Prepare chroot environment (QEMU, Debian 7.0, armel).
9 |
10 | 2) Get e2fsprogs (includes mke2fs and e2fsck):
11 |
12 | ```
13 | apt-get build-dep e2fsprogs
14 | apt-get source e2fsprogs
15 | ```
16 |
17 | 3) Apply patch:
18 |
19 | ```
20 | patch ./e2fsprogs-1.42.5/lib/ext2fs/ismounted.c ./ismounted.patch
21 | ```
22 |
23 | 4) Build e2fsprogs:
24 |
25 | ```
26 | cd ./e2fsprogs-1.42.5
27 | ./configure
28 | make CFLAGS=-static LDFLAGS=-static
29 | strip -s ./misc/mke2fs
30 | strip -s ./e2fsck/e2fsck
31 | ```
32 |
33 | 5) Copy mke2fs and e2fsck binary to assets directory.
34 |
35 |
--------------------------------------------------------------------------------
/contrib/e2fsprogs/ismounted.patch:
--------------------------------------------------------------------------------
1 | --- a/lib/ext2fs/ismounted.c 2017-12-22 16:13:08.000000000 +0300
2 | +++ b/lib/ext2fs/ismounted.c 2017-12-22 16:15:10.000000000 +0300
3 | @@ -348,7 +348,7 @@
4 | */
5 | errcode_t ext2fs_check_if_mounted(const char *file, int *mount_flags)
6 | {
7 | - return ext2fs_check_mount_point(file, mount_flags, NULL, 0);
8 | + return 0; // ext2fs_check_mount_point(file, mount_flags, NULL, 0);
9 | }
10 |
11 | #ifdef DEBUG
12 |
13 |
--------------------------------------------------------------------------------
/contrib/pkgdetails/README.md:
--------------------------------------------------------------------------------
1 | Build pkgdetails
2 | ================
3 |
4 | $ ndk-build
5 | $ mv libs/armeabi/pkgdetails ../../app/src/main/assets/bin/arm/pkgdetails
6 | $ mv libs/x86/pkgdetails ../../app/src/main/assets/bin/intel/pkgdetails
7 |
8 |
--------------------------------------------------------------------------------
/contrib/pkgdetails/jni/Android.mk:
--------------------------------------------------------------------------------
1 | LOCAL_PATH := $(call my-dir)
2 |
3 | include $(CLEAR_VARS)
4 |
5 | LOCAL_CFLAGS = -static
6 | LOCAL_LDFLAGS = -static
7 |
8 | LOCAL_MODULE := pkgdetails
9 | LOCAL_SRC_FILES := pkgdetails.c
10 |
11 | include $(BUILD_EXECUTABLE)
12 |
--------------------------------------------------------------------------------
/contrib/pkgdetails/jni/Application.mk:
--------------------------------------------------------------------------------
1 | APP_ABI := armeabi x86
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 | android.enableJetifier=true
20 | android.useAndroidX=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meefik/linuxdeploy/d2f1af07a2f4880eae7da0d33b79691716ff9883/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Mar 01 19:19:59 MSK 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------