Compare commits

..

2 Commits

Author SHA1 Message Date
Caten
86ce2315d4 Add signal 9 info, adjust readme 2023-11-07 17:29:49 +08:00
Caten
6e51e5b2d2 fix tab key eaten 2023-10-19 23:45:33 +08:00
4 changed files with 74 additions and 175 deletions

View File

@@ -40,6 +40,8 @@ assets的文件来源如下:
- 启动时会尝试挂载手机的一些字体目录(AppFiles/Fonts、Fonts和/system/fonts), 如果这些目录下有字体文件的话会一并加载到系统中,无需额外安装;
- 最后采用tar.xz压缩用split命令分成了xa*等多个文件(低内存设备一次性拷贝大文件会导致软件闪退)。
完整的容器制作过程可以在[这里](https://github.com/Cateners/build-tiny-rootfs)看到。
数据包不再在assets中更新而是随releases提供主要是为了避免git越来越大
lib目录
@@ -51,6 +53,20 @@ lib目录
- G 全局变量类
- Workflow 从软件点开到容器启动的所有步骤
## 编译
你需要配置好flutter和安卓sdk然后克隆此项目。
在编译之前需要在release中下载系统rootfs(或者[自行制作](https://github.com/Cateners/build-tiny-rootfs))之后使用split命令分割拷贝到assets。一般我将其分为98MB。
`split -b 98M debian.tar.xz`
然后修改workflow的代码找到复制资源的部分把生成的xa\*名字写进去(我还不知道怎么写代码识别有多少个xa*文件)
接下来就可以编译了。我使用的命令如下:
`flutter build apk --target-platform android-arm64 --split-per-abi --obfuscate --split-debug-info=tiny_computer/sdi`
## 目前已知bug
多用户/分身情形无法sudo, 其它见issue

View File

@@ -27,7 +27,6 @@ import 'package:clipboard/clipboard.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter_pty/flutter_pty.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:saf/saf.dart';
//import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/material.dart';
@@ -848,7 +847,7 @@ class _MyHomePageState extends State<MyHomePage> {
bannerAdsFailedToLoad = true;
});
},
), Expanded(flex: 1, child: AnimatedSwitcher(
), Expanded(child: AnimatedSwitcher(
duration: const Duration(milliseconds: 256),
child: [
Column(children: [Expanded(child: forceScaleGestureDetector(onScaleUpdate: (details) {
@@ -858,8 +857,7 @@ class _MyHomePageState extends State<MyHomePage> {
}, onScaleEnd: (details) async {
await G.prefs.setDouble("termFontScale", G.termFontScale);
}, child: TerminalView(G.termPtys[G.currentContainer]!.terminal, textScaleFactor: G.termFontScale, keyboardType: TextInputType.multiline,))),
(Util.getGlobal("isTerminalCommandsEnabled") as bool)?Padding(padding: const EdgeInsets.all(8), child:
SingleChildScrollView(restorationId: "commands-bar", scrollDirection: Axis.horizontal, child: Row(children: [AnimatedBuilder(
(Util.getGlobal("isTerminalCommandsEnabled") as bool)?Padding(padding: const EdgeInsets.all(8), child: Row(children: [AnimatedBuilder(
animation: G.keyboard,
builder: (context, child) => ToggleButtons(
constraints: const BoxConstraints(minWidth: 32, minHeight: 24),
@@ -881,54 +879,15 @@ class _MyHomePageState extends State<MyHomePage> {
},
children: const [Text('Ctrl'), Text('Alt'), Text('Shift')],
),
//TODO: 丑陋的实现,不知道列表有没有更方便的操作
), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.escape);
}, child: const Text("Esc")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.tab);
}, child: const Text("Tab")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.arrowUp);
}, child: const Text("")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.arrowDown);
}, child: const Text("")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.arrowLeft);
}, child: const Text("")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.arrowRight);
}, child: const Text("")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.delete);
}, child: const Text("Del")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.pageUp);
}, child: const Text("PgUp")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.pageDown);
}, child: const Text("PgDn")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.home);
}, child: const Text("Home")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.end);
}, child: const Text("End")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.f1);
}, child: const Text("F1")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.f2);
}, child: const Text("F2")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.f3);
}, child: const Text("F3")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.f4);
}, child: const Text("F4")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.f5);
}, child: const Text("F5")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.f6);
}, child: const Text("F6")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.f7);
}, child: const Text("F7")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.f8);
}, child: const Text("F8")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.f9);
}, child: const Text("F9")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.f10);
}, child: const Text("F10")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.f11);
}, child: const Text("F11")), SizedBox.fromSize(size: const Size.square(4)), OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(TerminalKey.f12);
}, child: const Text("F12")), SizedBox.fromSize(size: const Size(72, 0))]))):SizedBox.fromSize(size: const Size.square(0))
),
SizedBox.fromSize(size: const Size.square(8)),
Expanded(child: SizedBox(height: 24, child: ListView.separated(scrollDirection: Axis.horizontal, itemBuilder:(context, index) {
return OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(D.termCommands[index]["key"]! as TerminalKey);
}, child: Text(D.termCommands[index]["name"]! as String));
}, separatorBuilder:(context, index) {
return SizedBox.fromSize(size: const Size.square(4));
}, itemCount: D.termCommands.length))), SizedBox.fromSize(size: const Size(72, 0))])):SizedBox.fromSize(size: const Size.square(0))
]), Padding(
padding: const EdgeInsets.all(8),
child: Scrollbar(child: SingleChildScrollView(restorationId: "control-scroll", child: Column(
@@ -1248,120 +1207,7 @@ class _MyHomePageState extends State<MyHomePage> {
Permission.manageExternalStorage.request();
}),
]),
const Text("这里可以将设备上的文件夹与软件容器内的文件夹绑定,在下次启动软件时生效。"),
ListView.builder(itemBuilder: (context, index) {
final Map<String, dynamic> e = jsonDecode(Util.getGlobal("customMounts")[index]);
return GestureDetector(onLongPress: () {
String name = e["name"]!;
bool isNameValid = false;
Saf pathSaf = Saf(e["path"]!);
bool isPathValid = false;
showDialog(context: context, builder: (context) {
return AlertDialog(title: const Text("选项编辑"), content: SingleChildScrollView(child: Column(children: [
TextFormField(initialValue: name, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "挂载到主文件夹的名称"), validator: (value) {
if (!RegExp("[^\\s\\\\/:\\*\\?\\\"<>\\|](\\x20|[^\\s\\\\/:\\*\\?\\\"<>\\|])*[^\\s\\\\/:\\*\\?\\\"<>\\|\\.]\$").hasMatch(value!)) {
return "非法文件名";
}
isNameValid = true;
return null;
}),
SizedBox.fromSize(size: const Size.square(8)),
TextFormField(maxLines: null, initialValue: pathSaf.directory, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "在本机的路径"), readOnly: true, onTap: () async {
isPathValid = (await pathSaf.getDirectoryPermission(isDynamic: true))??false;
}),
])), actions: [
TextButton(onPressed:() async {
await G.prefs.setStringList("customMounts", Util.getGlobal("customMounts")
..removeAt(index));
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
}, child: const Text("删除该项")),
TextButton(onPressed:() {
Navigator.of(context).pop();
}, child: const Text("取消")),
TextButton(onPressed:() async {
if (!isNameValid || !isPathValid) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("名称非法或路径无效"))
);
return;
}
await G.prefs.setStringList("customMounts", Util.getGlobal("customMounts")
..setAll(index, [{"name": name, "path": pathSaf.directory, "isEnabled": e["isEnabled"]}]));
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
}, child: const Text("保存")),
]);
},);
}, child: CheckboxListTile(title: Text(e["name"]), subtitle: Text(e["path"]), value: e["isEnabled"], onChanged: (value) async {
await G.prefs.setStringList("customMounts", Util.getGlobal("customMounts")..setAll(index, [jsonEncode(e..update("isEnabled", (v) {
return value!;
}))]));
setState(() {});
}));
}, shrinkWrap: true, itemCount: Util.getGlobal("customMounts").length),
ListTile(title: const Text("添加路径"), onTap: () async {
Saf pathSaf = Saf("/storage/self/primary/Download");
bool hasPermission = (await pathSaf.getDirectoryPermission(isDynamic: true))??false;
if (!hasPermission) {
return;
}
String name = "新路径";
bool isNameValid = false;
bool isPathValid = false;
if (!context.mounted) return;
showDialog(context: context, builder: (context) {
return AlertDialog(title: const Text("选项编辑"), content: SingleChildScrollView(child: Column(children: [
TextFormField(initialValue: name, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "挂载到主文件夹的名称"), validator: (value) {
if (!RegExp("[^\\s\\\\/:\\*\\?\\\"<>\\|](\\x20|[^\\s\\\\/:\\*\\?\\\"<>\\|])*[^\\s\\\\/:\\*\\?\\\"<>\\|\\.]\$").hasMatch(value!)) {
return "非法文件名";
}
isNameValid = true;
return null;
}),
SizedBox.fromSize(size: const Size.square(8)),
TextFormField(maxLines: null, initialValue: pathSaf.directory, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "在本机的路径"), readOnly: true, onTap: () async {
isPathValid = (await pathSaf.getDirectoryPermission(isDynamic: true))??false;
}),
])), actions: [
TextButton(onPressed:() {
Navigator.of(context).pop();
}, child: const Text("取消")),
TextButton(onPressed:() async {
if (!isNameValid || !isPathValid) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("名称非法或路径无效"))
);
return;
}
await G.prefs.setStringList("customMounts", Util.getGlobal("customMounts")
..add({"name": name, "path": pathSaf.directory, "isEnabled": true}));
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
}, child: const Text("保存")),
]);
});
}, onLongPress: () {
showDialog(context: context, builder: (context) {
return AlertDialog(content: const Text("是否清空所有路径?"), actions: [
TextButton(onPressed:() {
Navigator.of(context).pop();
}, child: const Text("取消")),
TextButton(onPressed:() async {
await G.prefs.setStringList("customMounts", []);
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
}, child: const Text("")),
]);
});
})
SizedBox.fromSize(size: const Size.square(16)),
],))),
ExpansionPanel(
isExpanded: _expandState[4],

View File

@@ -91,8 +91,6 @@ class Util {
// bind:[{name:"U盘", src:"/storage/xxxx", dst:"/media/meow"}]...]}
//String[] adsBonus: 观看广告获取的奖励(json)
//{name: "xxx", amount: xxx}
//String[] customMounts: 自定义挂载地址(json)
//{name: "xxx", path: "xxx", isEnabled: true}
//TODO: 这么写还是不对劲,有空改成类试试?
static dynamic getGlobal(String key) {
bool b = G.prefs.containsKey(key);
@@ -110,10 +108,9 @@ class Util {
case "termFontScale" : return b ? G.prefs.getDouble(key)! : (value){G.prefs.setDouble(key, value); return value;}(1.0);
case "vip" : return b ? G.prefs.getInt(key)! : (value){G.prefs.setInt(key, value); return value;}(0);
case "isStickyKey" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(true);
case "defaultFFmpegCommand" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("-hide_banner -an -max_delay 1000000 -r 30 -f android_camera -i 0:0 -vf scale=iw/2:-1 -rtsp_transport udp -f rtsp rtsp://127.0.0.1:8554/stream");
case "defaultFFmpegCommand" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("-hide_banner -an -max_delay 1000000 -r 30 -f android_camera -camera_index 0 -i 0:0 -vf scale=iw/2:-1 -rtsp_transport udp -f rtsp rtsp://127.0.0.1:8554/stream");
case "containersInfo" : return G.prefs.getStringList(key)!;
case "adsBonus" : return b ? G.prefs.getStringList(key)! : (value){G.prefs.setStringList(key, value); return value;}([].cast<String>());
case "customMounts" : return b ? G.prefs.getStringList(key)! : (value){G.prefs.setStringList(key, value); return value;}([].cast<String>());
}
}
@@ -270,6 +267,7 @@ class TermPty {
Navigator.push(G.homePageStateContext, MaterialPageRoute(builder: (context) {
const TextStyle ts = TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.normal);
const String helperLink = "https://www.vmos.cn/zhushou.htm";
const String helperLink2 = "https://b23.tv/WwqOqW6";
return Scaffold(backgroundColor: Colors.deepPurple,
body: Center(
child: Scrollbar(child:
@@ -288,7 +286,18 @@ class TermPty {
},))
);
});
}, child: const Text("复制", style: ts, textAlign: TextAlign.center))
}, child: const Text("复制", style: ts, textAlign: TextAlign.center)),
const Text("如果不能解决请参考此教程: ", style: ts, textAlign: TextAlign.center),
OutlinedButton(onPressed: () {
FlutterClipboard.copy(helperLink2).then(( value ) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: const Text("已复制"), action: SnackBarAction(label: "跳转", onPressed: () {
launchUrl(Uri.parse(helperLink2), mode: LaunchMode.externalApplication);
},))
);
});
}, child: const Text("查看", style: ts, textAlign: TextAlign.center))
]),
)
)
@@ -329,7 +338,7 @@ class D {
{"name":"卸载视频剪辑软件Kdenlive", "command":"sudo apt autoremove --purge -y kdenlive"},
{"name":"安装科学计算软件Octave", "command":"sudo apt update && sudo apt install -y octave"},
{"name":"卸载科学计算软件Octave", "command":"sudo apt autoremove --purge -y octave"},
{"name":"安装WPS", "command":"wget https://wps-linux-personal.wpscdn.cn/wps/download/ep/Linux2019/11704/wps-office_11.1.0.11704_arm64.deb -O /tmp/wps.deb && sudo apt update && sudo apt install -y /tmp/wps.deb; rm /tmp/wps.deb"},
{"name":"安装WPS", "command":"wget https://wps-linux-personal.wpscdn.cn/wps/download/ep/Linux2019/11708/wps-office_11.1.0.11708_arm64.deb -O /tmp/wps.deb && sudo apt update && sudo apt install -y /tmp/wps.deb; rm /tmp/wps.deb"},
{"name":"卸载WPS", "command":"sudo apt autoremove --purge -y wps-office"},
{"name":"安装CAJViewer", "command":"wget https://download.cnki.net/net.cnki.cajviewer_1.3.20-1_arm64.deb -O /tmp/caj.deb && sudo apt update && sudo apt install -y /tmp/caj.deb && bash /home/tiny/.local/share/tiny/caj/postinst; rm /tmp/caj.deb"},
{"name":"卸载CAJViewer", "command":"sudo apt autoremove --purge -y net.cnki.cajviewer && bash /home/tiny/.local/share/tiny/caj/postrm"},
@@ -343,6 +352,32 @@ class D {
{"name":"拉流测试", "command":"ffplay rtsp://127.0.0.1:8554/stream &"},
{"name":"关机", "command":"stopvnc\nexit\nexit"},
{"name":"???", "command":"timeout 8 cmatrix"}];
//默认快捷指令
static const termCommands = [
{"name": "Esc", "key": TerminalKey.escape},
{"name": "Tab", "key": TerminalKey.tab},
{"name": "", "key": TerminalKey.arrowUp},
{"name": "", "key": TerminalKey.arrowDown},
{"name": "", "key": TerminalKey.arrowLeft},
{"name": "", "key": TerminalKey.arrowRight},
{"name": "Del", "key": TerminalKey.delete},
{"name": "PgUp", "key": TerminalKey.pageUp},
{"name": "PgDn", "key": TerminalKey.pageDown},
{"name": "Home", "key": TerminalKey.home},
{"name": "End", "key": TerminalKey.end},
{"name": "F1", "key": TerminalKey.f1},
{"name": "F2", "key": TerminalKey.f2},
{"name": "F3", "key": TerminalKey.f3},
{"name": "F4", "key": TerminalKey.f4},
{"name": "F5", "key": TerminalKey.f5},
{"name": "F6", "key": TerminalKey.f6},
{"name": "F7", "key": TerminalKey.f7},
{"name": "F8", "key": TerminalKey.f8},
{"name": "F9", "key": TerminalKey.f9},
{"name": "F10", "key": TerminalKey.f10},
{"name": "F11", "key": TerminalKey.f11},
{"name": "F12", "key": TerminalKey.f12},
];
}
// Global variables
@@ -524,6 +559,7 @@ ln -s \$DATA_DIR/busybox \$DATA_DIR/bin/xz
"""
export DATA_DIR=${G.dataPath}
export CONTAINER_DIR=\$DATA_DIR/containers/0
export EXTRA_OPT=""
cd \$DATA_DIR
export PATH=\$DATA_DIR/bin:\$PATH
export PROOT_TMP_DIR=\$DATA_DIR/proot_tmp
@@ -551,7 +587,7 @@ done
//$DATA_DIR是数据文件夹, $CONTAINER_DIR是容器根目录
await G.prefs.setStringList("containersInfo", ["""{
"name":"Debian Bookworm",
"boot":"\$DATA_DIR/bin/proot -H --change-id=1000:1000 --pwd=/home/tiny --rootfs=\$CONTAINER_DIR --mount=/system --mount=/apex --kill-on-exit --mount=/storage:/storage --sysvipc -L --link2symlink --mount=/proc:/proc --mount=/dev:/dev --mount=\$CONTAINER_DIR/tmp:/dev/shm --mount=/dev/urandom:/dev/random --mount=/proc/self/fd:/dev/fd --mount=/proc/self/fd/0:/dev/stdin --mount=/proc/self/fd/1:/dev/stdout --mount=/proc/self/fd/2:/dev/stderr --mount=/dev/null:/dev/tty0 --mount=/dev/null:/proc/sys/kernel/cap_last_cap --mount=/storage/self/primary:/media/sd --mount=\$DATA_DIR/share:/home/tiny/公共 --mount=/storage/self/primary/Fonts:/usr/share/fonts/wpsm --mount=/storage/self/primary/AppFiles/Fonts:/usr/share/fonts/yozom --mount=/system/fonts:/usr/share/fonts/androidm --mount=/storage/self/primary/Pictures:/home/tiny/图片 --mount=/storage/self/primary/Music:/home/tiny/音乐 --mount=/storage/self/primary/Movies:/home/tiny/视频 --mount=/storage/self/primary/Download:/home/tiny/下载 --mount=/storage/self/primary/DCIM:/home/tiny/照片 --mount=/storage/self/primary/Documents:/home/tiny/文档 --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/.tmoe-container.stat:/proc/stat --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/.tmoe-container.version:/proc/version --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/bus:/proc/bus --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/buddyinfo:/proc/buddyinfo --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/cgroups:/proc/cgroups --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/consoles:/proc/consoles --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/crypto:/proc/crypto --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/devices:/proc/devices --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/diskstats:/proc/diskstats --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/execdomains:/proc/execdomains --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/fb:/proc/fb --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/filesystems:/proc/filesystems --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/interrupts:/proc/interrupts --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/iomem:/proc/iomem --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/ioports:/proc/ioports --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/kallsyms:/proc/kallsyms --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/keys:/proc/keys --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/key-users:/proc/key-users --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/kpageflags:/proc/kpageflags --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/loadavg:/proc/loadavg --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/locks:/proc/locks --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/misc:/proc/misc --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/modules:/proc/modules --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/pagetypeinfo:/proc/pagetypeinfo --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/partitions:/proc/partitions --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/sched_debug:/proc/sched_debug --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/softirqs:/proc/softirqs --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/timer_list:/proc/timer_list --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/uptime:/proc/uptime --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/vmallocinfo:/proc/vmallocinfo --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/vmstat:/proc/vmstat --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/zoneinfo:/proc/zoneinfo /usr/bin/env -i HOSTNAME=TINY HOME=/home/tiny USER=tiny TERM=xterm-256color SDL_IM_MODULE=fcitx XMODIFIERS=@im=fcitx QT_IM_MODULE=fcitx GTK_IM_MODULE=fcitx TMOE_CHROOT=false TMOE_PROOT=true TMPDIR=/tmp MOZ_FAKE_NO_SANDBOX=1 DISPLAY=:4 PULSE_SERVER=tcp:127.0.0.1:4718 LANG=zh_CN.UTF-8 SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/games:/usr/local/games /bin/bash -l",
"boot":"\$DATA_DIR/bin/proot -H --change-id=1000:1000 --pwd=/home/tiny --rootfs=\$CONTAINER_DIR --mount=/system --mount=/apex --kill-on-exit --mount=/storage:/storage --sysvipc -L --link2symlink --mount=/proc:/proc --mount=/dev:/dev --mount=\$CONTAINER_DIR/tmp:/dev/shm --mount=/dev/urandom:/dev/random --mount=/proc/self/fd:/dev/fd --mount=/proc/self/fd/0:/dev/stdin --mount=/proc/self/fd/1:/dev/stdout --mount=/proc/self/fd/2:/dev/stderr --mount=/dev/null:/dev/tty0 --mount=/dev/null:/proc/sys/kernel/cap_last_cap --mount=/storage/self/primary:/media/sd --mount=\$DATA_DIR/share:/home/tiny/公共 --mount=/storage/self/primary/Fonts:/usr/share/fonts/wpsm --mount=/storage/self/primary/AppFiles/Fonts:/usr/share/fonts/yozom --mount=/system/fonts:/usr/share/fonts/androidm --mount=/storage/self/primary/Pictures:/home/tiny/图片 --mount=/storage/self/primary/Music:/home/tiny/音乐 --mount=/storage/self/primary/Movies:/home/tiny/视频 --mount=/storage/self/primary/Download:/home/tiny/下载 --mount=/storage/self/primary/DCIM:/home/tiny/照片 --mount=/storage/self/primary/Documents:/home/tiny/文档 --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/.tmoe-container.stat:/proc/stat --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/.tmoe-container.version:/proc/version --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/bus:/proc/bus --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/buddyinfo:/proc/buddyinfo --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/cgroups:/proc/cgroups --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/consoles:/proc/consoles --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/crypto:/proc/crypto --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/devices:/proc/devices --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/diskstats:/proc/diskstats --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/execdomains:/proc/execdomains --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/fb:/proc/fb --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/filesystems:/proc/filesystems --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/interrupts:/proc/interrupts --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/iomem:/proc/iomem --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/ioports:/proc/ioports --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/kallsyms:/proc/kallsyms --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/keys:/proc/keys --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/key-users:/proc/key-users --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/kpageflags:/proc/kpageflags --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/loadavg:/proc/loadavg --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/locks:/proc/locks --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/misc:/proc/misc --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/modules:/proc/modules --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/pagetypeinfo:/proc/pagetypeinfo --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/partitions:/proc/partitions --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/sched_debug:/proc/sched_debug --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/softirqs:/proc/softirqs --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/timer_list:/proc/timer_list --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/uptime:/proc/uptime --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/vmallocinfo:/proc/vmallocinfo --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/vmstat:/proc/vmstat --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/zoneinfo:/proc/zoneinfo /usr/bin/env -i HOSTNAME=TINY HOME=/home/tiny USER=tiny TERM=xterm-256color SDL_IM_MODULE=fcitx XMODIFIERS=@im=fcitx QT_IM_MODULE=fcitx GTK_IM_MODULE=fcitx TMOE_CHROOT=false TMOE_PROOT=true TMPDIR=/tmp MOZ_FAKE_NO_SANDBOX=1 DISPLAY=:4 PULSE_SERVER=tcp:127.0.0.1:4718 LANG=zh_CN.UTF-8 SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/games:/usr/local/games \$EXTRA_OPT /bin/bash -l",
"vnc":"startnovnc &",
"vncUrl":"http://localhost:36082/vnc.html?host=localhost&port=36082&autoconnect=true&resize=remote&password=12345678",
"commands":${jsonEncode(D.commands)}
@@ -698,7 +734,8 @@ clear""");
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.arrowUp,
LogicalKeyboardKey.arrowDown
LogicalKeyboardKey.arrowDown,
LogicalKeyboardKey.tab
}.contains(event.logicalKey)) {
return KeyEventResult.skipRemainingHandlers;
}

View File

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.10+1
version: 1.0.10+2
environment:
sdk: '>=3.1.0 <4.0.0'