mirror of
https://github.com/Cateners/tiny_computer.git
synced 2026-05-20 08:35:46 +08:00
Microphone support
This commit is contained in:
@@ -107,6 +107,12 @@ android {
|
|||||||
//checkReleaseBuilds false
|
//checkReleaseBuilds false
|
||||||
abortOnError false
|
abortOnError false
|
||||||
}
|
}
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path "src/main/cpp/CMakeLists.txt"
|
||||||
|
version "3.22.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
pickFirst 'lib/arm64-v8a/libc++_shared.so'
|
pickFirst 'lib/arm64-v8a/libc++_shared.so'
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
|||||||
11
android/app/src/main/cpp/CMakeLists.txt
Normal file
11
android/app/src/main/cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.18.1)
|
||||||
|
project("native-socket")
|
||||||
|
|
||||||
|
# 添加库
|
||||||
|
add_library(native-socket SHARED native-socket.cpp)
|
||||||
|
|
||||||
|
# 链接日志库
|
||||||
|
find_library(log-lib log)
|
||||||
|
|
||||||
|
# 指定目标属性
|
||||||
|
target_link_libraries(native-socket ${log-lib})
|
||||||
78
android/app/src/main/cpp/native-socket.cpp
Normal file
78
android/app/src/main/cpp/native-socket.cpp
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#include <jni.h>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <android/log.h>
|
||||||
|
#include <cerrno>
|
||||||
|
|
||||||
|
#define LOG_TAG "NativeAudio"
|
||||||
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
|
||||||
|
int server_fd = -1;
|
||||||
|
int client_fd = -1;
|
||||||
|
|
||||||
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
|
Java_com_example_tiny_1computer_AudioStream_nativeInit(JNIEnv *env, jobject thiz, jstring path) {
|
||||||
|
const char *socket_path = env->GetStringUTFChars(path, 0);
|
||||||
|
|
||||||
|
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (server_fd == -1) {
|
||||||
|
LOGE("Socket creation failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_un addr;
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
|
||||||
|
unlink(socket_path); // Remove existing file if any
|
||||||
|
|
||||||
|
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||||
|
LOGE("Bind failed: %s", strerror(errno));
|
||||||
|
close(server_fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen(server_fd, 1) == -1) {
|
||||||
|
LOGE("Listen failed");
|
||||||
|
close(server_fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
env->ReleaseStringUTFChars(path, socket_path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
|
Java_com_example_tiny_1computer_AudioStream_nativeAccept(JNIEnv *env, jobject thiz) {
|
||||||
|
if (server_fd == -1) return -1;
|
||||||
|
// Blocks here until Linux connects
|
||||||
|
client_fd = accept(server_fd, NULL, NULL);
|
||||||
|
if (client_fd == -1) {
|
||||||
|
LOGE("Accept failed: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
|
Java_com_example_tiny_1computer_AudioStream_nativeSend(JNIEnv *env, jobject thiz, jbyteArray data, jint size) {
|
||||||
|
if (client_fd == -1) return -1;
|
||||||
|
|
||||||
|
jbyte *buffer = env->GetByteArrayElements(data, NULL);
|
||||||
|
ssize_t sent = write(client_fd, buffer, size);
|
||||||
|
env->ReleaseByteArrayElements(data, buffer, JNI_ABORT);
|
||||||
|
|
||||||
|
if (sent == -1 && errno != EAGAIN) {
|
||||||
|
LOGE("Write failed (Broken Pipe?)");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" JNIEXPORT void JNICALL
|
||||||
|
Java_com_example_tiny_1computer_AudioStream_nativeClose(JNIEnv *env, jobject thiz) {
|
||||||
|
if (client_fd != -1) { close(client_fd); client_fd = -1; }
|
||||||
|
if (server_fd != -1) { close(server_fd); server_fd = -1; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package com.example.tiny_computer
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.media.AudioFormat
|
||||||
|
import android.media.AudioRecord
|
||||||
|
import android.media.MediaRecorder
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
object AudioStream {
|
||||||
|
init {
|
||||||
|
System.loadLibrary("native-socket")
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isStreaming = false
|
||||||
|
private var recordingThread: Thread? = null
|
||||||
|
|
||||||
|
// Native functions
|
||||||
|
private external fun nativeInit(path: String): Int
|
||||||
|
private external fun nativeAccept(): Int
|
||||||
|
private external fun nativeSend(data: ByteArray, size: Int): Int
|
||||||
|
private external fun nativeClose()
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission") // Ensure RECORD_AUDIO is granted in Manifest
|
||||||
|
fun startStreaming(path: String) {
|
||||||
|
if (isStreaming) return
|
||||||
|
isStreaming = true
|
||||||
|
|
||||||
|
recordingThread = Thread {
|
||||||
|
// 1. Initialize Socket Server
|
||||||
|
if (nativeInit(path) < 0) {
|
||||||
|
Log.e("AudioStream", "Failed to bind socket")
|
||||||
|
isStreaming = false
|
||||||
|
return@Thread
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Wait for Linux client to connect (Blocking)
|
||||||
|
Log.d("AudioStream", "Waiting for connection on $path...")
|
||||||
|
if (nativeAccept() < 0) {
|
||||||
|
Log.e("AudioStream", "Accept failed")
|
||||||
|
isStreaming = false
|
||||||
|
return@Thread
|
||||||
|
}
|
||||||
|
Log.d("AudioStream", "Client connected!")
|
||||||
|
|
||||||
|
// 3. Setup AudioRecord
|
||||||
|
val sampleRate = 44100
|
||||||
|
val bufferSize = AudioRecord.getMinBufferSize(
|
||||||
|
sampleRate,
|
||||||
|
AudioFormat.CHANNEL_IN_MONO,
|
||||||
|
AudioFormat.ENCODING_PCM_16BIT
|
||||||
|
)
|
||||||
|
|
||||||
|
val recorder = AudioRecord(
|
||||||
|
MediaRecorder.AudioSource.MIC,
|
||||||
|
sampleRate,
|
||||||
|
AudioFormat.CHANNEL_IN_MONO,
|
||||||
|
AudioFormat.ENCODING_PCM_16BIT,
|
||||||
|
bufferSize
|
||||||
|
)
|
||||||
|
|
||||||
|
val data = ByteArray(bufferSize)
|
||||||
|
recorder.startRecording()
|
||||||
|
|
||||||
|
val discardMillis = 5000 // 丢弃前5秒
|
||||||
|
val discardBytes = (sampleRate * 2 * discardMillis / 1000).toInt() // 16bit = 2字节
|
||||||
|
var bytesRead = 0
|
||||||
|
|
||||||
|
// 先读取并丢弃初始数据
|
||||||
|
while (bytesRead < discardBytes && isStreaming) {
|
||||||
|
val readBytes = recorder.read(data, 0, minOf(bufferSize, discardBytes - bytesRead))
|
||||||
|
if (readBytes > 0) {
|
||||||
|
bytesRead += readBytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Streaming Loop
|
||||||
|
while (isStreaming) {
|
||||||
|
val readBytes = recorder.read(data, 0, bufferSize)
|
||||||
|
if (readBytes > 0) {
|
||||||
|
val sent = nativeSend(data, readBytes)
|
||||||
|
if (sent < 0) break // Socket broken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
recorder.stop()
|
||||||
|
recorder.release()
|
||||||
|
nativeClose()
|
||||||
|
}
|
||||||
|
recordingThread?.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopStreaming() {
|
||||||
|
isStreaming = false
|
||||||
|
nativeClose() // Unblocks the native Accept/Send if hung
|
||||||
|
recordingThread?.join()
|
||||||
|
recordingThread = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,12 @@ class MainActivity: FlutterActivity() {
|
|||||||
"getNativeLibraryPath" -> {
|
"getNativeLibraryPath" -> {
|
||||||
result.success(getApplicationInfo().nativeLibraryDir)
|
result.success(getApplicationInfo().nativeLibraryDir)
|
||||||
}
|
}
|
||||||
|
"startStreaming" -> {
|
||||||
|
AudioStream.startStreaming(call.argument("path")!!)
|
||||||
|
}
|
||||||
|
"stopStreaming" -> {
|
||||||
|
AudioStream.stopStreaming()
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// 不支持的方法名
|
// 不支持的方法名
|
||||||
result.notImplemented()
|
result.notImplemented()
|
||||||
|
|||||||
74
extra/tiny_virtual_mic.c
Normal file
74
extra/tiny_virtual_mic.c
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <pulse/simple.h>
|
||||||
|
#include <pulse/error.h>
|
||||||
|
|
||||||
|
#define BUFSIZE 4096
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc != 3) {
|
||||||
|
fprintf(stderr, "Usage: %s <socket_path> <target_device>\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Setup PulseAudio
|
||||||
|
static const pa_sample_spec ss = {
|
||||||
|
.format = PA_SAMPLE_S16LE,
|
||||||
|
.rate = 44100,
|
||||||
|
.channels = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
pa_buffer_attr attr;
|
||||||
|
attr.maxlength = (uint32_t) -1;
|
||||||
|
attr.tlength = pa_usec_to_bytes(60000, &ss); // 目标延迟设为 60ms
|
||||||
|
attr.prebuf = (uint32_t) -1;
|
||||||
|
attr.minreq = (uint32_t) -1;
|
||||||
|
attr.fragsize = (uint32_t) -1;
|
||||||
|
|
||||||
|
int error;
|
||||||
|
pa_simple *s = pa_simple_new(NULL, "AndroidStream", PA_STREAM_PLAYBACK, argv[2], "live_audio", &ss, NULL, &attr, &error);
|
||||||
|
if (!s) {
|
||||||
|
fprintf(stderr, "pa_simple_new() failed: %s\n", pa_strerror(error));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Connect to Android Socket
|
||||||
|
int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (sock_fd < 0) {
|
||||||
|
perror("socket");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_un addr;
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
strncpy(addr.sun_path, argv[1], sizeof(addr.sun_path) - 1);
|
||||||
|
|
||||||
|
printf("Connecting to %s...\n", argv[1]);
|
||||||
|
while (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||||
|
printf("Waiting for server...\n");
|
||||||
|
sleep(1); // Retry logic since Android might start later
|
||||||
|
}
|
||||||
|
printf("Connected! Playing audio...\n");
|
||||||
|
|
||||||
|
// 3. Stream Loop
|
||||||
|
uint8_t buf[BUFSIZE];
|
||||||
|
while (1) {
|
||||||
|
ssize_t r = read(sock_fd, buf, sizeof(buf));
|
||||||
|
if (r <= 0) break;
|
||||||
|
|
||||||
|
if (pa_simple_write(s, buf, (size_t)r, &error) < 0) {
|
||||||
|
fprintf(stderr, "pa_simple_write() failed: %s\n", pa_strerror(error));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
pa_simple_free(s);
|
||||||
|
close(sock_fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -109,5 +109,7 @@
|
|||||||
"updateRequest": "Please try to use the latest version. Visit the project address to check for the latest version.",
|
"updateRequest": "Please try to use the latest version. Visit the project address to check for the latest version.",
|
||||||
"avncScreenResize": "Adaptive Screen Size",
|
"avncScreenResize": "Adaptive Screen Size",
|
||||||
"avncResizeFactor": "Screen Scaling Ratio",
|
"avncResizeFactor": "Screen Scaling Ratio",
|
||||||
"avncResizeFactorValue": "Current scaling is"
|
"avncResizeFactorValue": "Current scaling is",
|
||||||
|
"microphoneSupport": "Microphone Support",
|
||||||
|
"startStreaming": "Start Microphone"
|
||||||
}
|
}
|
||||||
@@ -109,5 +109,7 @@
|
|||||||
"updateRequest": "请尽量使用最新版本。前往项目地址可查看最新版本。",
|
"updateRequest": "请尽量使用最新版本。前往项目地址可查看最新版本。",
|
||||||
"avncScreenResize": "自适应屏幕尺寸",
|
"avncScreenResize": "自适应屏幕尺寸",
|
||||||
"avncResizeFactor": "屏幕缩放比",
|
"avncResizeFactor": "屏幕缩放比",
|
||||||
"avncResizeFactorValue": "当前缩放为"
|
"avncResizeFactorValue": "当前缩放为",
|
||||||
|
"microphoneSupport": "麦克风支持",
|
||||||
|
"startStreaming": "开启麦克风"
|
||||||
}
|
}
|
||||||
@@ -109,5 +109,7 @@
|
|||||||
"updateRequest": "請盡量使用最新版本。前往專案網址查看最新版本。",
|
"updateRequest": "請盡量使用最新版本。前往專案網址查看最新版本。",
|
||||||
"avncScreenResize": "自適應螢幕尺寸",
|
"avncScreenResize": "自適應螢幕尺寸",
|
||||||
"avncResizeFactor": "螢幕縮放比",
|
"avncResizeFactor": "螢幕縮放比",
|
||||||
"avncResizeFactorValue": "目前縮放為"
|
"avncResizeFactorValue": "目前縮放為",
|
||||||
|
"microphoneSupport": "麥克風支援",
|
||||||
|
"startStreaming": "開啟麥克風"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ class SettingPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _SettingPageState extends State<SettingPage> {
|
class _SettingPageState extends State<SettingPage> {
|
||||||
|
|
||||||
final List<bool> _expandState = [false, false, false, false, false, false];
|
final List<bool> _expandState = [false, false, false, false, false, false, false];
|
||||||
|
|
||||||
double _avncScaleFactor = Util.getGlobal("avncScaleFactor") as double;
|
double _avncScaleFactor = Util.getGlobal("avncScaleFactor") as double;
|
||||||
|
|
||||||
@@ -602,6 +602,37 @@ sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""");
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
},),
|
},),
|
||||||
],))),
|
],))),
|
||||||
|
ExpansionPanel(
|
||||||
|
isExpanded: _expandState[6],
|
||||||
|
headerBuilder: ((context, isExpanded) {
|
||||||
|
return ListTile(title: Text(AppLocalizations.of(context)!.microphoneSupport), subtitle: Text(AppLocalizations.of(context)!.experimentalFeature));
|
||||||
|
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
|
||||||
|
const SizedBox.square(dimension: 16),
|
||||||
|
SwitchListTile(title: Text(AppLocalizations.of(context)!.startStreaming), value: G.isStreaming, onChanged:(value) async {
|
||||||
|
if (value) {
|
||||||
|
await Permission.microphone.request();
|
||||||
|
if (await Permission.microphone.isGranted) {
|
||||||
|
String path = "/tmp/android_audio";
|
||||||
|
D.androidChannel.invokeMethod("startStreaming", {"path": "${G.dataPath}/containers/${G.currentContainer}$path"});
|
||||||
|
Util.termWrite("""
|
||||||
|
pactl load-module module-null-sink sink_name=AndroidSink sink_properties=device.description="Android_Audio_Stream"
|
||||||
|
pactl load-module module-remap-source master=AndroidSink.monitor source_name=AndroidMic source_properties=device.description="Android_Virtual_Mic"
|
||||||
|
pkill -f tiny_virtual_mic
|
||||||
|
tiny_virtual_mic $path AndroidSink &""");
|
||||||
|
G.pageIndex.value = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Util.termWrite("""
|
||||||
|
pactl list short modules | grep "Android" | cut -f1 | xargs -L1 pactl unload-module
|
||||||
|
pkill -f tiny_virtual_mic""");
|
||||||
|
G.pageIndex.value = 0;
|
||||||
|
D.androidChannel.invokeMethod("stopStreaming", {});
|
||||||
|
}
|
||||||
|
G.isStreaming = value;
|
||||||
|
setState(() {});
|
||||||
|
},),
|
||||||
|
const SizedBox.square(dimension: 16),
|
||||||
|
],))),
|
||||||
],);
|
],);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ class Util {
|
|||||||
case "avncResizeDesktop" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(true);
|
case "avncResizeDesktop" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(true);
|
||||||
case "avncScaleFactor" : return b ? G.prefs.getDouble(key)!.clamp(-1.0, 1.0) : (value){G.prefs.setDouble(key, value); return value;}(-0.5);
|
case "avncScaleFactor" : return b ? G.prefs.getDouble(key)!.clamp(-1.0, 1.0) : (value){G.prefs.setDouble(key, value); return value;}(-0.5);
|
||||||
case "useX11" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
|
case "useX11" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
|
||||||
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 "defaultVirglCommand" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("--use-egl-surfaceless --use-gles --socket-path=\$CONTAINER_DIR/tmp/.virgl_test");
|
case "defaultVirglCommand" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("--use-egl-surfaceless --use-gles --socket-path=\$CONTAINER_DIR/tmp/.virgl_test");
|
||||||
case "defaultVirglOpt" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("GALLIUM_DRIVER=virpipe");
|
case "defaultVirglOpt" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("GALLIUM_DRIVER=virpipe");
|
||||||
case "defaultTurnipOpt" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("MESA_LOADER_DRIVER_OVERRIDE=zink VK_ICD_FILENAMES=/home/tiny/.local/share/tiny/extra/freedreno_icd.aarch64.json TU_DEBUG=noconform");
|
case "defaultTurnipOpt" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("MESA_LOADER_DRIVER_OVERRIDE=zink VK_ICD_FILENAMES=/home/tiny/.local/share/tiny/extra/freedreno_icd.aarch64.json TU_DEBUG=noconform");
|
||||||
@@ -512,11 +511,7 @@ class G {
|
|||||||
static late VirtualKeyboard keyboard; //存储ctrl, shift, alt状态
|
static late VirtualKeyboard keyboard; //存储ctrl, shift, alt状态
|
||||||
static bool maybeCtrlJ = false; //为了区分按下的ctrl+J和enter而准备的变量
|
static bool maybeCtrlJ = false; //为了区分按下的ctrl+J和enter而准备的变量
|
||||||
static ValueNotifier<double> termFontScale = ValueNotifier(1); //终端字体大小,存储为G.prefs的termFontScale
|
static ValueNotifier<double> termFontScale = ValueNotifier(1); //终端字体大小,存储为G.prefs的termFontScale
|
||||||
static bool isStreamServerStarted = false;
|
|
||||||
static bool isStreaming = false;
|
static bool isStreaming = false;
|
||||||
//static int? streamingPid;
|
|
||||||
static String streamingOutput = "";
|
|
||||||
static late Pty streamServerPty;
|
|
||||||
//static int? virglPid;
|
//static int? virglPid;
|
||||||
static ValueNotifier<int> pageIndex = ValueNotifier(0); //主界面索引
|
static ValueNotifier<int> pageIndex = ValueNotifier(0); //主界面索引
|
||||||
static ValueNotifier<bool> terminalPageChange = ValueNotifier(true); //更改值,用于刷新小键盘
|
static ValueNotifier<bool> terminalPageChange = ValueNotifier(true); //更改值,用于刷新小键盘
|
||||||
@@ -527,7 +522,6 @@ class G {
|
|||||||
static bool wasAvncEnabled = false;
|
static bool wasAvncEnabled = false;
|
||||||
static bool wasX11Enabled = false;
|
static bool wasX11Enabled = false;
|
||||||
|
|
||||||
|
|
||||||
static late SharedPreferences prefs;
|
static late SharedPreferences prefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -776,6 +770,7 @@ ${G.dataPath}/bin/virgl_test_server ${Util.getGlobal("defaultVirglCommand")}""")
|
|||||||
}
|
}
|
||||||
extraMount += "--mount=\$DATA_DIR/tiny/font:/usr/share/fonts/tiny ";
|
extraMount += "--mount=\$DATA_DIR/tiny/font:/usr/share/fonts/tiny ";
|
||||||
extraMount += "--mount=\$DATA_DIR/tiny/extra/cmatrix:/home/tiny/.local/bin/cmatrix ";
|
extraMount += "--mount=\$DATA_DIR/tiny/extra/cmatrix:/home/tiny/.local/bin/cmatrix ";
|
||||||
|
extraMount += "--mount=\$DATA_DIR/tiny/extra/tiny_virtual_mic:/home/tiny/.local/bin/tiny_virtual_mic ";
|
||||||
Util.termWrite(
|
Util.termWrite(
|
||||||
"""
|
"""
|
||||||
export DATA_DIR=${G.dataPath}
|
export DATA_DIR=${G.dataPath}
|
||||||
|
|||||||
Reference in New Issue
Block a user