|
@@ -11,7 +11,7 @@ const { dialog } = require('electron'); // 引入 electron 的 dialog 模块
|
|
|
const CoreWindow = require("ee-core/electron/window");
|
|
const CoreWindow = require("ee-core/electron/window");
|
|
|
|
|
|
|
|
|
|
|
|
|
-const { readConfigFile, writeConfigFile } = require('../utils/config');
|
|
|
|
|
|
|
+const { readConfigFile, updateConfigFile } = require('../utils/config');
|
|
|
const defaultConfig = require('./config.default');
|
|
const defaultConfig = require('./config.default');
|
|
|
|
|
|
|
|
// 进程状态管理
|
|
// 进程状态管理
|
|
@@ -72,12 +72,67 @@ function findSmartShooter5Dir(config) {
|
|
|
return ""
|
|
return ""
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function saveControllerChoice(type, dir) {
|
|
|
|
|
+ const spec = CONTROLLERS[type]
|
|
|
|
|
+ if (!spec || !dir) return
|
|
|
|
|
+ updateConfigFile({
|
|
|
|
|
+ controlType: type,
|
|
|
|
|
+ controlPath: dir,
|
|
|
|
|
+ [spec.pathKey]: dir,
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function controllerChoiceMatches(config, type, dir) {
|
|
|
|
|
+ const spec = CONTROLLERS[type]
|
|
|
|
|
+ if (!spec) return false
|
|
|
|
|
+ return config.controlType === type
|
|
|
|
|
+ && sameDir(config.controlPath, dir)
|
|
|
|
|
+ && sameDir(config[spec.pathKey], dir)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function uniqueDirs(dirs) {
|
|
|
|
|
+ const result = []
|
|
|
|
|
+ for (const dir of dirs) {
|
|
|
|
|
+ if (!dir) continue
|
|
|
|
|
+ if (result.some((item) => sameDir(item, dir))) continue
|
|
|
|
|
+ result.push(dir)
|
|
|
|
|
+ }
|
|
|
|
|
+ return result
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function rememberController(config, detected, dir) {
|
|
|
|
|
+ if (!controllerChoiceMatches(config, detected, dir)) {
|
|
|
|
|
+ Log.info(`按安装目录识别为 ${CONTROLLERS[detected].label}: ${dir}`)
|
|
|
|
|
+ saveControllerChoice(detected, dir)
|
|
|
|
|
+ }
|
|
|
|
|
+ return {
|
|
|
|
|
+ type: detected,
|
|
|
|
|
+ ...CONTROLLERS[detected],
|
|
|
|
|
+ dir,
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function getController() {
|
|
function getController() {
|
|
|
const config = readConfigFile() || {}
|
|
const config = readConfigFile() || {}
|
|
|
const type = normalizeControlType(config.controlType)
|
|
const type = normalizeControlType(config.controlType)
|
|
|
const spec = CONTROLLERS[type]
|
|
const spec = CONTROLLERS[type]
|
|
|
const dir = config.controlPath || config[spec.pathKey] || ""
|
|
const dir = config.controlPath || config[spec.pathKey] || ""
|
|
|
|
|
|
|
|
|
|
+ // 上次选中的目录里有 exe 时,以那个 exe 为准。
|
|
|
|
|
+ // 避免 controlPath 已是 Smart Shooter 6,controlType 仍是 digiCamControl。
|
|
|
|
|
+ const selected = detectController(config.controlPath)
|
|
|
|
|
+ if (selected) return rememberController(config, selected, config.controlPath)
|
|
|
|
|
+
|
|
|
|
|
+ // controlPath 无效时,再找该类型自己的目录和出厂目录,避免坏路径挡住已经装好的程序。
|
|
|
|
|
+ const fallbackDirs = uniqueDirs([
|
|
|
|
|
+ config[spec.pathKey],
|
|
|
|
|
+ defaultConfig[spec.pathKey],
|
|
|
|
|
+ type === "SmartShooter6" ? defaultConfig.controlPath : "",
|
|
|
|
|
+ ])
|
|
|
|
|
+ for (const candidate of fallbackDirs) {
|
|
|
|
|
+ if (detectController(candidate) === type) return rememberController(config, type, candidate)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 默认 Smart Shooter 6 目录下没有程序时,再找 Smart Shooter 5。只影响本次启动,不回写配置。
|
|
// 默认 Smart Shooter 6 目录下没有程序时,再找 Smart Shooter 5。只影响本次启动,不回写配置。
|
|
|
if (type === "SmartShooter6" && isDefaultSmartShooter6Dir(dir) && !fs.existsSync(path.join(dir, spec.exe))) {
|
|
if (type === "SmartShooter6" && isDefaultSmartShooter6Dir(dir) && !fs.existsSync(path.join(dir, spec.exe))) {
|
|
|
const ss5Dir = findSmartShooter5Dir(config)
|
|
const ss5Dir = findSmartShooter5Dir(config)
|
|
@@ -109,6 +164,7 @@ function getExePath () {
|
|
|
|
|
|
|
|
// 用户手动选安装目录时,按目录里实际存在的 exe 识别,5 和 6 互不覆盖
|
|
// 用户手动选安装目录时,按目录里实际存在的 exe 识别,5 和 6 互不覆盖
|
|
|
function detectController(dir) {
|
|
function detectController(dir) {
|
|
|
|
|
+ if (!dir) return ""
|
|
|
if (fs.existsSync(path.join(dir, CONTROLLERS.SmartShooter6.exe))) return "SmartShooter6"
|
|
if (fs.existsSync(path.join(dir, CONTROLLERS.SmartShooter6.exe))) return "SmartShooter6"
|
|
|
if (fs.existsSync(path.join(dir, CONTROLLERS.SmartShooter.exe))) return "SmartShooter"
|
|
if (fs.existsSync(path.join(dir, CONTROLLERS.SmartShooter.exe))) return "SmartShooter"
|
|
|
if (fs.existsSync(path.join(dir, CONTROLLERS.digiCamControl.exe))) return "digiCamControl"
|
|
if (fs.existsSync(path.join(dir, CONTROLLERS.digiCamControl.exe))) return "digiCamControl"
|
|
@@ -118,7 +174,18 @@ function detectController(dir) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+let ensureControllerPromise = null
|
|
|
|
|
+
|
|
|
async function checkCameraControlCmdExists() {
|
|
async function checkCameraControlCmdExists() {
|
|
|
|
|
+ if (!ensureControllerPromise) {
|
|
|
|
|
+ ensureControllerPromise = ensureCameraController().finally(() => {
|
|
|
|
|
+ ensureControllerPromise = null
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ return ensureControllerPromise
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function ensureCameraController() {
|
|
|
try {
|
|
try {
|
|
|
|
|
|
|
|
// 拼接 CameraControlCmd.exe 的完整路径
|
|
// 拼接 CameraControlCmd.exe 的完整路径
|
|
@@ -131,10 +198,14 @@ async function checkCameraControlCmdExists() {
|
|
|
|
|
|
|
|
if (!exists) {
|
|
if (!exists) {
|
|
|
// 弹出文件夹选择对话框
|
|
// 弹出文件夹选择对话框
|
|
|
- const { canceled, filePaths } = await dialog.showOpenDialog({
|
|
|
|
|
|
|
+ const dialogOptions = {
|
|
|
title: '选择 相机控制安装软件 文件夹',
|
|
title: '选择 相机控制安装软件 文件夹',
|
|
|
properties: ['openDirectory']
|
|
properties: ['openDirectory']
|
|
|
- });
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ const mainWindow = CoreWindow.getMainWindow()
|
|
|
|
|
+ const { canceled, filePaths } = mainWindow && !mainWindow.isDestroyed()
|
|
|
|
|
+ ? await dialog.showOpenDialog(mainWindow, dialogOptions)
|
|
|
|
|
+ : await dialog.showOpenDialog(dialogOptions);
|
|
|
|
|
|
|
|
if (!canceled && filePaths.length > 0) {
|
|
if (!canceled && filePaths.length > 0) {
|
|
|
const selectedPath = filePaths[0];
|
|
const selectedPath = filePaths[0];
|
|
@@ -147,9 +218,7 @@ async function checkCameraControlCmdExists() {
|
|
|
msg:"所选目录中未找到 SmartShooter6.exe、SmartShooter5.exe 或 CameraControl.exe",
|
|
msg:"所选目录中未找到 SmartShooter6.exe、SmartShooter5.exe 或 CameraControl.exe",
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- writeConfigFile("controlType", detected)
|
|
|
|
|
- writeConfigFile("controlPath", selectedPath)
|
|
|
|
|
- writeConfigFile(CONTROLLERS[detected].pathKey, selectedPath)
|
|
|
|
|
|
|
+ saveControllerChoice(detected, selectedPath)
|
|
|
} else {
|
|
} else {
|
|
|
console.error('用户未选择文件夹');
|
|
console.error('用户未选择文件夹');
|
|
|
return {
|
|
return {
|
|
@@ -180,55 +249,56 @@ async function checkCameraControlCmdExists() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
-async function openCameraControlCmd(digiCamControlPath) {
|
|
|
|
|
- return new Promise(async (resolve, reject) => {
|
|
|
|
|
- try {
|
|
|
|
|
- // 获取 digiCamControl 文件夹路径
|
|
|
|
|
-
|
|
|
|
|
- // 拼接 CameraControlCmd.exe 的完整路径
|
|
|
|
|
- let exePath = getExePath()
|
|
|
|
|
|
|
+function openCameraControlCmd() {
|
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
|
+ const exePath = getExePath()
|
|
|
|
|
+ fs.promises.access(exePath, fs.constants.F_OK).then(() => {
|
|
|
|
|
+ let settled = false
|
|
|
|
|
+ const finish = (fn, value) => {
|
|
|
|
|
+ if (settled) return
|
|
|
|
|
+ settled = true
|
|
|
|
|
+ fn(value)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 检查文件是否存在
|
|
|
|
|
- await fs.promises.access(exePath, fs.constants.F_OK);
|
|
|
|
|
|
|
+ let child
|
|
|
try {
|
|
try {
|
|
|
|
|
+ child = spawn(exePath, [], {
|
|
|
|
|
+ cwd: path.dirname(exePath),
|
|
|
|
|
+ stdio: 'ignore',
|
|
|
|
|
+ })
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ Log.error('无法启动第三方相机控制器:', error)
|
|
|
|
|
+ finish(reject, error)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 保存进程引用并标记为运行中
|
|
|
|
|
- cameraProcess = spawn(exePath);
|
|
|
|
|
- isProcessRunning = true;
|
|
|
|
|
- console.log('[CAMERA-DEBUG] spawn PID:', cameraProcess.pid, '时间:', new Date().toISOString());
|
|
|
|
|
-
|
|
|
|
|
- cameraProcess.stdout.on('data', (data) => {
|
|
|
|
|
- console.log('[CAMERA-DEBUG] stdout 收到数据:', data.toString().substring(0, 100));
|
|
|
|
|
- resolve(true)
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 监听进程关闭事件
|
|
|
|
|
- cameraProcess.on('close', (code) => {
|
|
|
|
|
- console.log('[CAMERA-DEBUG] close 事件触发! PID:', cameraProcess?.pid, '退出码:', code, '时间:', new Date().toISOString());
|
|
|
|
|
- isProcessRunning = false;
|
|
|
|
|
- cameraProcess = null;
|
|
|
|
|
- Log.info(`相机控制软件已关闭,退出码: ${code}`);
|
|
|
|
|
- processEmitter.emit('camera-process-closed');
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- cameraProcess.on('error', (err) => {
|
|
|
|
|
- console.log('[CAMERA-DEBUG] error 事件触发:', err.message);
|
|
|
|
|
- isProcessRunning = false;
|
|
|
|
|
- cameraProcess = null;
|
|
|
|
|
- Log.error('相机控制软件进程错误:', err);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
|
|
+ cameraProcess = child
|
|
|
|
|
+ isProcessRunning = true
|
|
|
|
|
+ console.log('[CAMERA-DEBUG] spawn PID:', child.pid, 'cwd:', path.dirname(exePath), '时间:', new Date().toISOString());
|
|
|
|
|
|
|
|
|
|
+ child.on('close', (code) => {
|
|
|
|
|
+ console.log('[CAMERA-DEBUG] close 事件触发! PID:', cameraProcess?.pid, '退出码:', code, '时间:', new Date().toISOString());
|
|
|
|
|
+ isProcessRunning = false;
|
|
|
|
|
+ cameraProcess = null;
|
|
|
|
|
+ Log.info(`相机控制软件已关闭,退出码: ${code}`);
|
|
|
|
|
+ processEmitter.emit('camera-process-closed');
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- Log.error('error 第三方相机控制器:', error);
|
|
|
|
|
- throw error;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ child.on('error', (err) => {
|
|
|
|
|
+ console.log('[CAMERA-DEBUG] error 事件触发:', err.message);
|
|
|
|
|
+ isProcessRunning = false;
|
|
|
|
|
+ cameraProcess = null;
|
|
|
|
|
+ Log.error('相机控制软件进程错误:', err);
|
|
|
|
|
+ finish(reject, err)
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
|
|
+ // 图形界面程序通常没有 stdout,进程拉起即视为启动成功
|
|
|
|
|
+ child.once('spawn', () => finish(resolve, true))
|
|
|
|
|
+ if (child.pid) finish(resolve, true)
|
|
|
|
|
+ }).catch((error) => {
|
|
|
Log.error('无法找到或运行 第三方相机控制器:', error);
|
|
Log.error('无法找到或运行 第三方相机控制器:', error);
|
|
|
- throw error;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ reject(error)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|