import 'dart:io'; import 'dart:async'; import 'package:dart_framework/utils/path.dart'; import 'package:dart_framework/utils/system_util.dart'; import 'package:path/path.dart' as path; import 'package:dart_framework/platform/process.dart'; class OSStartup { static _Startup? _instance; static _Startup? get _current { if (_instance == null) { if (Platform.isMacOS) { _instance = _StartupMac(); } else if (Platform.isLinux) { _instance = _StartupLinux(); } else { _instance = _StartupWindows(); } } return _instance; } static Future isRegistedStartup(String label) async { return _current!.isRegistedStartup(label); } //shell file must have '#!/bin/sh' static Future registStartup(String executableFilePath, String label, {List? arguments, String? logStandardFilePath, String? logErrorFilePath}) async { return _current!.registStartup(executableFilePath, label, arguments: arguments, logStandardFilePath: logStandardFilePath, logErrorFilePath: logErrorFilePath); } static Future unregistStartup(String label) async { return _current!.unregistStartup(label); } } class _Startup { Future isRegistedStartup(String label) async { var c = Completer(); return c.future; } //shell file must have '#!/bin/sh' Future registStartup(String executableFilePath, String label, {List? arguments, String? logStandardFilePath, String? logErrorFilePath}) async { var c = Completer(); return c.future; } Future unregistStartup(String label) async { var c = Completer(); return c.future; } } /// ************* MAC *************** class _StartupMac extends _Startup { final plistTemplate = """ KeepAlive Label {LABEL} ProgramArguments {FILE_PATH} RunAtLoad {LOG_STANDARD}{LOG_ERROR} """; final pathTemplate = """#!/bin/bash # Load .zprofile if it exists if [ -f ~/.zprofile ]; then source ~/.zprofile fi # Load .bash_profile if it exists if [ -f ~/.bash_profile ]; then source ~/.bash_profile fi # Load .bashrc if it exists (optional) if [ -f ~/.bashrc ]; then source ~/.bashrc fi # Load .zshrc if it exists (optional) if [ -f ~/.zshrc ]; then source ~/.zshrc fi # Run your command {COMMAND} """; final logStandardTemplate = """ StandardOutPath {LOG_STANDARD}"""; final logErrorTemplate = """ StandardErrorPath {LOG_ERROR}"""; @override Future isRegistedStartup(String label) async { var c = Completer(); var plistName = '$label.plist'; var userPath = await Path.userPath; c.complete( await File('$userPath/Library/LaunchAgents/$plistName').exists()); return c.future; } //shell file must have '#!/bin/sh' @override Future registStartup(String executableFilePath, String label, {List? arguments, String? logStandardFilePath, String? logErrorFilePath}) async { final tagLabel = '{LABEL}'; final tagPath = '{FILE_PATH}'; final plistName = '$label.plist'; final userPath = await Path.userPath; var success = false; final appDir = Directory('$userPath/Library/$label'); final file = File('${appDir.path}/launch.sh'); if(!appDir.existsSync()) { appDir.createSync(recursive: true); } var param = arguments == null ? '' : ' ${arguments.join(' ')}'; var launchContent = pathTemplate.replaceAll('{COMMAND}', '$executableFilePath$param'); file.writeAsStringSync(launchContent); //create plist in LaunchAgents final tagLogStandard = '{LOG_STANDARD}'; final tagLogError = '{LOG_ERROR}'; var plistContent = plistTemplate .replaceAll(tagLabel, label) .replaceAll(tagPath, file.path); if (logStandardFilePath == null) { plistContent = plistContent.replaceAll(tagLogStandard, ''); } else { plistContent = plistContent.replaceAll(tagLogStandard, logStandardTemplate.replaceAll(tagLogStandard, logStandardFilePath)); } if (logErrorFilePath == null) { plistContent = plistContent.replaceAll(tagLogError, ''); } else { plistContent = plistContent.replaceAll(tagLogError, logErrorTemplate.replaceAll(tagLogError, logErrorFilePath)); } var plistFile = File('$userPath/Library/LaunchAgents/$plistName'); await plistFile.create(); await plistFile.writeAsString(plistContent); var result = await ProcessExecutor.run( StringBuffer('chmod +x "$executableFilePath" && chmod +x "${file.path}" ')); success = result.exitCode == 0; return dataFutrue(success); } @override Future unregistStartup(String label) async { var c = Completer(); var success = false; var plistName = '$label.plist'; var userPath = await Path.userPath; var file = File('$userPath/Library/LaunchAgents/$plistName'); if (file.existsSync()) { await file.delete(); success = true; } c.complete(success); return c.future; } } /// ************* LINUX *************** class _StartupLinux extends _Startup { final serviceTemplate = """[Unit] Description={LABEL} #Documentation=http://toki-labs.com [Service] Type=simple #User=deck #Group=deck #TimeoutStartSec=0 #Restart=on-failure #RestartSec=30s #ExecStartPre= ExecStart={FILE_PATH} #SyslogIdentifier=TestService #ExecStop= [Install] WantedBy=default.target"""; @override Future isRegistedStartup(String label) async { var c = Completer(); var exist = false; var userPath = await Path.userPath; exist = await File('$userPath/.config/systemd/user/$label.service').exists(); c.complete(exist); return c.future; } //shell file must have '#!/bin/sh' @override Future registStartup(String executableFilePath, String label, {List? arguments, String? logStandardFilePath, String? logErrorFilePath}) async { var c = Completer(); final tagLabel = '{LABEL}'; final tagPath = '{FILE_PATH}'; ProcessResult? result; var userPath = await Path.userPath; var systemPath = '$userPath/.config/systemd/user'; if (arguments != null) { for (var arg in arguments) { executableFilePath += ' $arg'; } } var serviceContent = serviceTemplate .replaceAll(tagLabel, label) .replaceAll(tagPath, executableFilePath); var serviceFile = File('$systemPath/$label.service'); await serviceFile.create(); await serviceFile.writeAsString(serviceContent); var shell = StringBuffer(); shell.write('cd $systemPath && '); shell.write('systemctl --user enable $label && '); shell.write('systemctl --user daemon-reload'); result = await ProcessExecutor.run(shell); print('exitCode: ${result.exitCode}'); c.complete(result.exitCode == 0); return c.future; } @override Future unregistStartup(String label) async { var c = Completer(); var success = false; var userPath = await Path.userPath; var shell = StringBuffer(); var systemPath = '$userPath/.config/systemd/user'; shell.write('cd $systemPath && '); shell.write('systemctl --user stop $label && '); shell.write('systemctl --user disable $label && '); shell.write('systemctl --user daemon-reload'); var result = await ProcessExecutor.run(shell); success = result.exitCode == 0; if (success) { success = false; var file = File('$systemPath/$label.service'); if (await file.exists()) { await file.delete(); success = true; } } c.complete(success); return c.future; } } /// ************* WINDOWS *************** class _StartupWindows extends _Startup { final windowsStartupPath = '\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\'; final windowRoamingPath = '\\AppData\\Roaming\\'; final vbsTemplate = """Set WshShell = CreateObject("WScript.Shell") WshShell.Run chr(34) & "{FILE_PATH}" & Chr(34) & {PARAMETERS}, 0 Set WshShell = Nothing """; @override Future isRegistedStartup(String label) async { var c = Completer(); //For Windows 8, 10 var exist = false; var userPath = await Path.userPath; var startupDir = Directory('$userPath$windowsStartupPath'); var files = startupDir.listSync(); for (var fileEntry in files) { var fileName = path.basename(fileEntry.path); var extension = path.extension(fileName); fileName = fileName.replaceAll(extension, ''); if (fileName == label) { exist = true; break; } } c.complete(exist); return c.future; } //shell file must have '#!/bin/sh' @override Future registStartup(String executableFilePath, String label, {List? arguments, String? logStandardFilePath, String? logErrorFilePath}) async { var c = Completer(); var file = File(executableFilePath); ProcessResult? result; //For Windows 8, 10 var userPath = Platform.environment['UserProfile']; //compose vbs content var parameters = ''; if (arguments != null) { for (var arg in arguments) { parameters += '" $arg"'; } } var vbsContent = vbsTemplate .replaceAll('{FILE_PATH}', executableFilePath) .replaceAll('{PARAMETERS}', parameters); //create vbs file var vbsInstallDir = Directory('$userPath$windowRoamingPath$label'); if (!await vbsInstallDir.exists()) { await vbsInstallDir.create(recursive: true); } var vbsFile = File(path.join(vbsInstallDir.path, '$label.vbs')); if (await vbsFile.exists()) { await vbsFile.delete(); } await vbsFile.create(); await vbsFile.writeAsString(vbsContent); //create symbolic var symbolicPath = '$userPath$windowsStartupPath$label.vbs'; result = await ProcessExecutor.run( StringBuffer('mklink "$symbolicPath" "${vbsFile.path}"')); c.complete(result.exitCode == 0); return c.future; } @override Future unregistStartup(String label) async { var c = Completer(); var success = false; //For Windows 8, 10 var userPath = Platform.environment['UserProfile']; //delete vbs file var vbsInstallDir = Directory('$userPath$windowRoamingPath$label'); var vbsFile = File(path.join(vbsInstallDir.path, '$label.vbs')); if (await vbsFile.exists()) { await vbsFile.delete(); var list = vbsInstallDir.listSync(recursive: true); if (list.isEmpty) { if (await vbsInstallDir.exists()) { await vbsInstallDir.delete(recursive: true); } } } //delete shorcut file var startupDir = Directory('$userPath$windowsStartupPath'); var files = startupDir.listSync(); for (var fileEntry in files) { var fileName = path.basename(fileEntry.path); var extension = path.extension(fileName); fileName = fileName.replaceAll(extension, ''); if (fileName == label) { await File(fileEntry.path).delete(); success = true; break; } } c.complete(success); return c.future; } }