import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:quasar_jet/services/singbox_runtime.dart'; final class SingBoxService { SingBoxService({SingBoxRuntime? runtime}) : _runtime = runtime ?? SingBoxRuntime(); final SingBoxRuntime _runtime; final StreamController _logController = StreamController.broadcast(); Process? _process; Stream get logs => _logController.stream; bool get isRunning => _process != null; Future prepare() async { await _runtime.ensureBinary(); } Future start(String configJson) async { if (_process != null) { return true; } if (!Platform.isLinux) { throw UnsupportedError('SingBoxService supports Linux only'); } try { jsonDecode(configJson); } catch (_) { _logController.add('Config is not valid JSON for sing-box'); return false; } final String binaryPath = await _runtime.ensureBinary(); final String configPath = await _runtime.configPath(); await File(configPath).writeAsString(configJson, flush: true); final List command = [ 'pkexec', binaryPath, 'run', '-c', configPath, ]; try { _process = await Process.start(command.first, command.sublist(1)); _process!.stdout .transform(utf8.decoder) .listen((String data) => _logController.add(data)); _process!.stderr .transform(utf8.decoder) .listen((String data) => _logController.add('ERROR: $data')); _process!.exitCode.then((int code) { _logController.add('sing-box exited with code $code'); _process = null; }); return true; } catch (error) { _logController.add('Start failed: $error'); _process = null; return false; } } Future stop() async { final Process? process = _process; if (process == null) { return; } process.kill(ProcessSignal.sigterm); await process.exitCode.timeout(const Duration(seconds: 3), onTimeout: () { process.kill(ProcessSignal.sigkill); return -1; }); _process = null; } Future dispose() async { await stop(); await _logController.close(); } }