| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- // electron/utils/readConfig.js
- const path = require('path');
- const fs = require('fs');
- const Log = require('ee-core/log');
- const { spawn } = require('child_process');
- const { EventEmitter } = require('events');
- const { liveShow, liveHide, setParams, capture, getParams,CMD,captureLive,closeOtherWindow,minimizeSmartShooter } = require('../api/camera');
- const { dialog } = require('electron'); // 引入 electron 的 dialog 模块
- const CoreWindow = require("ee-core/electron/window");
- const { readConfigFile, updateConfigFile } = require('../utils/config');
- const defaultConfig = require('./config.default');
- // 进程状态管理
- let cameraProcess = null; // 子进程引用
- let isProcessRunning = false; // 进程运行状态
- const processEmitter = new EventEmitter(); // 进程事件发射器
- // Smart Shooter 5 / 6 与 digiCamControl 并存。
- // 5 和 6 共用同一套 External API(54543 事件 / 54544 请求),不能同时开,按 controlType 只启动其中一个。
- const CONTROLLERS = {
- digiCamControl: {
- exe: "CameraControl.exe",
- pathKey: "digiCamControlPath",
- label: "digiCamControl",
- },
- SmartShooter: {
- exe: "SmartShooter5.exe",
- pathKey: "SmartShooterPath",
- label: "Smart Shooter 5",
- },
- SmartShooter6: {
- exe: "SmartShooter6.exe",
- pathKey: "SmartShooter6Path",
- label: "Smart Shooter 6",
- },
- }
- // 历史配置 controlType 仍是 SmartShooter,保持原行为
- function normalizeControlType(type) {
- return CONTROLLERS[type] ? type : "SmartShooter"
- }
- function isSmartShooterType(type) {
- return type === "SmartShooter" || type === "SmartShooter6"
- }
- function sameDir(a, b) {
- if (!a || !b) return false
- const norm = (p) => path.normalize(String(p)).replace(/[\\/]+$/, "").toLowerCase()
- return norm(a) === norm(b)
- }
- // 只在仍使用出厂的 Smart Shooter 6 目录时回退,手动改过安装目录的保持原选择
- function isDefaultSmartShooter6Dir(dir) {
- return sameDir(dir, defaultConfig.SmartShooter6Path) || sameDir(dir, defaultConfig.controlPath)
- }
- function findSmartShooter5Dir(config) {
- const candidates = [config.SmartShooterPath, defaultConfig.SmartShooterPath]
- const seen = new Set()
- for (const dir of candidates) {
- if (!dir) continue
- const key = path.normalize(String(dir)).replace(/[\\/]+$/, "").toLowerCase()
- if (seen.has(key)) continue
- seen.add(key)
- if (fs.existsSync(path.join(dir, CONTROLLERS.SmartShooter.exe))) return dir
- }
- 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() {
- const config = readConfigFile() || {}
- const type = normalizeControlType(config.controlType)
- const spec = CONTROLLERS[type]
- 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。只影响本次启动,不回写配置。
- if (type === "SmartShooter6" && isDefaultSmartShooter6Dir(dir) && !fs.existsSync(path.join(dir, spec.exe))) {
- const ss5Dir = findSmartShooter5Dir(config)
- if (ss5Dir) {
- Log.info(`默认目录未找到 SmartShooter6.exe,改用 Smart Shooter 5: ${ss5Dir}`)
- return {
- type: "SmartShooter",
- ...CONTROLLERS.SmartShooter,
- dir: ss5Dir,
- }
- }
- }
- return {
- type,
- ...spec,
- dir,
- }
- }
- function getExePath () {
- const controller = getController()
- const exePath = controller.dir ? path.join(controller.dir, controller.exe) : ""
- console.log('ex============ePath');
- console.log(exePath);
- return exePath
- }
- // 用户手动选安装目录时,按目录里实际存在的 exe 识别,5 和 6 互不覆盖
- function detectController(dir) {
- if (!dir) return ""
- 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.digiCamControl.exe))) return "digiCamControl"
- return ""
- }
- let ensureControllerPromise = null
- async function checkCameraControlCmdExists() {
- if (!ensureControllerPromise) {
- ensureControllerPromise = ensureCameraController().finally(() => {
- ensureControllerPromise = null
- })
- }
- return ensureControllerPromise
- }
- async function ensureCameraController() {
- try {
- // 拼接 CameraControlCmd.exe 的完整路径
- let exePath = getExePath()
- // 检查文件是否存在
- const exists = await fs.promises.access(exePath, fs.constants.F_OK)
- .then(() => true)
- .catch(() => false);
- if (!exists) {
- // 弹出文件夹选择对话框
- const dialogOptions = {
- title: '选择 相机控制安装软件 文件夹',
- 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) {
- const selectedPath = filePaths[0];
- const detected = detectController(selectedPath);
- if (!detected) {
- console.error('所选目录不是已知的相机控制软件');
- return {
- status:-1,
- msg:"所选目录中未找到 SmartShooter6.exe、SmartShooter5.exe 或 CameraControl.exe",
- }
- }
- saveControllerChoice(detected, selectedPath)
- } else {
- console.error('用户未选择文件夹');
- return {
- status:-1,
- msg:"无法找到 SmartShooter6.exe、SmartShooter5.exe 或 CameraControl.exe",
- }
- }
- }
- const res = await openCameraControlCmd();
-
- // Smart Shooter 5 / 6 启动后最小化窗口;digiCamControl 走自己的最小化命令
- if(isSmartShooterType(getController().type)){
- // 等待软件启动完成
- setTimeout(async () => {
- try {
- await minimizeSmartShooter();
- } catch (error) {
- console.log('最小化SmartShooter失败:', error);
- }
- }, 3000); // 等待3秒让软件完全启动
- }
-
- return res;
- } catch (error) {
- Log.error('检查 第三方相机控制器 是否存在时出错:', error);
- throw error;
- }
- }
- 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)
- }
- let child
- try {
- child = spawn(exePath, [], {
- cwd: path.dirname(exePath),
- stdio: 'ignore',
- })
- } catch (error) {
- Log.error('无法启动第三方相机控制器:', error)
- finish(reject, error)
- return
- }
- 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');
- });
- child.on('error', (err) => {
- console.log('[CAMERA-DEBUG] error 事件触发:', err.message);
- isProcessRunning = false;
- cameraProcess = null;
- Log.error('相机控制软件进程错误:', err);
- finish(reject, err)
- });
- // 图形界面程序通常没有 stdout,进程拉起即视为启动成功
- child.once('spawn', () => finish(resolve, true))
- if (child.pid) finish(resolve, true)
- }).catch((error) => {
- Log.error('无法找到或运行 第三方相机控制器:', error);
- reject(error)
- })
- })
- }
- async function closeCameraControlTips() {
- try {
- await closeOtherWindow()
- }catch (e) {
- console.log(e)
- }
- }
- module.exports = {
- checkCameraControlCmdExists,
- closeCameraControlTips,
- minimizeSmartShooter,
- CONTROLLERS,
- isSmartShooterType,
- getController,
- // 进程状态管理
- isProcessRunning: () => isProcessRunning,
- onProcessClosed: (callback) => processEmitter.on('camera-process-closed', callback),
- removeProcessClosedListener: (callback) => processEmitter.removeListener('camera-process-closed', callback),
- };
|