初始化

This commit is contained in:
24kycj
2026-05-22 19:56:22 +08:00
commit d0c3fd162a
77 changed files with 17058 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
export const CS_OK = 0
export const CS_FAIL = -1
export const JOB_STATUS_FINISHED = 100
export const JOB_STATUS_FAILED = 4
export const JOB_STATUS_CANCELLED = 6
export const JOB_STATUS_WAITCARD = 7
export const JOB_STATUS_PRINTING = 2
export const JOB_STATUS_COPYING = 3
export const POLL_INTERVAL_MS = 1000
export const JOB_ID_BUF_SIZE = 64
/** workDll 日志级别:仅致命,屏蔽无打印机时的 E/W 刷屏 */
export const LOG_FATAL_FLAG = 3
+121
View File
@@ -0,0 +1,121 @@
import { app, BrowserWindow, globalShortcut, screen } from 'electron'
import { join } from 'path'
app.commandLine.appendSwitch('disable-gpu-shader-disk-cache')
import log from 'electron-log'
import { suppressKnownDllStderr } from './utils/suppress-dll-stderr'
import { setupNativeWorkingDir } from './services/native-path'
suppressKnownDllStderr()
import { registerIpcHandlers, handleBeforeQuit } from './ipc/register-handlers'
import { setPollMainWindow } from './services/poll-manager'
import {
DESIGN_WIDTH,
contentHeightForWidth,
CONTENT_VIEWPORT_HEIGHT
} from '@shared/viewport'
let mainWindow: BrowserWindow | null = null
const MIN_CONTENT_WIDTH = 960
/** 默认内容区:约 85% 工作区宽,高按 720:390 */
function getDefaultWindowSize(): { width: number; height: number } {
const { width: sw, height: sh } = screen.getPrimaryDisplay().workAreaSize
let w = Math.max(1280, Math.min(Math.floor(sw * 0.85), 1600))
let h = contentHeightForWidth(w)
const maxH = Math.floor(sh * 0.85)
if (h > maxH) {
h = Math.max(contentHeightForWidth(MIN_CONTENT_WIDTH), maxH)
w = Math.round((h * DESIGN_WIDTH) / CONTENT_VIEWPORT_HEIGHT)
}
return { width: w, height: h }
}
function createWindow(): void {
const { width, height } = getDefaultWindowSize()
mainWindow = new BrowserWindow({
useContentSize: true,
width,
height,
minWidth: MIN_CONTENT_WIDTH,
minHeight: contentHeightForWidth(MIN_CONTENT_WIDTH),
show: false,
autoHideMenuBar: true,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: false
}
})
setPollMainWindow(mainWindow)
mainWindow.on('ready-to-show', () => {
mainWindow?.center()
mainWindow?.show()
if (!app.isPackaged) {
mainWindow?.webContents.openDevTools({ mode: 'detach' })
}
})
mainWindow.webContents.on('before-input-event', (_event, input) => {
if (input.type === 'keyDown' && input.key === 'F12') {
mainWindow?.webContents.toggleDevTools()
}
})
// 内容区 720:390,画布仍 360 高,按宽缩放后底部可完整显示
mainWindow.on('resize', () => {
if (!mainWindow) return
const [cw, ch] = mainWindow.getContentSize()
const wantH = contentHeightForWidth(cw)
if (Math.abs(ch - wantH) > 2) {
mainWindow.setContentSize(cw, wantH)
}
})
mainWindow.on('closed', () => {
mainWindow = null
})
if (process.env.ELECTRON_RENDERER_URL) {
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL)
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
app.whenReady().then(() => {
try {
setupNativeWorkingDir()
registerIpcHandlers()
createWindow()
if (!app.isPackaged) {
globalShortcut.register('CommandOrControl+Shift+I', () => {
const win = BrowserWindow.getFocusedWindow()
win?.webContents.toggleDevTools()
})
}
} catch (e) {
log.error('startup failed', e)
app.quit()
}
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('before-quit', () => {
if (!app.isPackaged) {
globalShortcut.unregisterAll()
}
handleBeforeQuit()
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
+244
View File
@@ -0,0 +1,244 @@
import { app, dialog, ipcMain, shell } from 'electron'
import fs from 'fs'
import path from 'path'
import log from 'electron-log'
import { CS_FAIL, CS_OK } from '../constants'
import { assertNotBusy, assertReady, mainAppState } from '../services/app-state'
import { configStore } from '../services/config-store'
import {
getActiveJobId,
startJobPoll,
startUsbPoll,
stopAllPolls,
stopJobPoll,
stopUsbPoll
} from '../services/poll-manager'
import {
dllAdminJobCancel,
dllCopyFromUsb,
dllGetPrinterErrorStr,
dllGetPrinterInfo,
dllInit,
dllPrinterReject,
dllPrinterReset,
dllRestJobEx,
isCancelApiAvailable,
isRejectApiAvailable
} from '../services/work-dll.service'
function ok<T>(data?: T) {
return { ok: true as const, code: CS_OK, data }
}
function fail(code: number, message: string) {
return { ok: false as const, code, message }
}
export function registerIpcHandlers(): void {
ipcMain.handle('dll:init', (_e, params) => {
stopAllPolls()
try {
const sharedDir = params?.sharedDir || (configStore.get('sharedDir') as string)
fs.mkdirSync(sharedDir, { recursive: true })
const code = dllInit({
sharedDir,
keepCombinedImage: params?.keepCombinedImage,
stopOnFailure: params?.stopOnFailure,
cleanTaskFile: params?.cleanTaskFile,
autoRetryTimes: params?.autoRetryTimes,
rejectConfig: params?.rejectConfig,
logLevel: params?.logLevel,
outBack: params?.outBack
})
if (code === CS_OK) {
mainAppState.initialized = true
configStore.set('sharedDir', sharedDir)
return ok({ printerDetected: true })
}
mainAppState.initialized = true
configStore.set('sharedDir', sharedDir)
log.warn(`SAPI_Init returned ${code}; UI ready, printer ops may fail until device connected`)
return ok({
printerDetected: false,
warning: '打印机未连接或驱动未就绪,界面可浏览,业务操作需接真机后重试 Init'
})
} catch (err) {
mainAppState.initialized = false
log.error('dll:init', err)
return fail(CS_FAIL, String(err))
}
})
ipcMain.handle('dll:printer-info', () => {
try {
assertReady()
const r = dllGetPrinterInfo()
if (!r.json) return fail(0, '未连接打印机')
return ok(r.json)
} catch (err) {
return fail(CS_FAIL, String(err))
}
})
ipcMain.handle('dll:printer-reset', () => {
try {
assertReady()
const code = dllPrinterReset()
return code === CS_OK ? ok() : fail(code, '重置失败')
} catch (err) {
return fail(CS_FAIL, String(err))
}
})
ipcMain.handle('dll:printer-reject', () => {
try {
assertReady()
if (!isRejectApiAvailable()) return fail(CS_FAIL, 'REJECT_API_UNAVAILABLE')
const code = dllPrinterReject()
return code === CS_OK ? ok() : fail(code, '废卡失败')
} catch (err) {
return fail(CS_FAIL, String(err))
}
})
ipcMain.handle('dll:printer-error-str', (_e, errorNo?: number) => {
try {
assertReady()
return ok({ text: dllGetPrinterErrorStr(errorNo ?? -1) })
} catch (err) {
return fail(CS_FAIL, String(err))
}
})
ipcMain.handle('dll:job-create', (_e, json: string) => {
try {
assertReady()
assertNotBusy()
const r = dllRestJobEx(json)
if (r.code !== CS_OK) return fail(r.code, 'RestJobEx 失败')
mainAppState.mode = 'distributing'
mainAppState.activeJobId = r.jobId
return ok({ jobId: r.jobId })
} catch (err) {
if (String(err).includes('BUSY')) return fail(CS_FAIL, '已有任务在执行')
return fail(CS_FAIL, String(err))
}
})
ipcMain.handle('dll:job-cancel', (_e, jobId: string) => {
try {
assertReady()
const id = jobId || mainAppState.activeJobId
stopJobPoll()
let code = CS_OK
if (isCancelApiAvailable()) {
code = dllAdminJobCancel(id)
} else {
log.info('dll:job-cancel: SAPI_AdminJobCancel not in DLL, poll stopped only')
}
mainAppState.mode = 'ready'
mainAppState.activeJobId = ''
return code === CS_OK ? ok() : fail(code, '取消失败')
} catch (err) {
return fail(CS_FAIL, String(err))
}
})
ipcMain.handle('dll:usb-copy', (_e, req: { destFolder: string; cardOutput: number }) => {
try {
assertReady()
assertNotBusy()
const code = dllCopyFromUsb(req.destFolder, req.cardOutput)
if (code !== CS_OK) {
return fail(code, '可能已有任务在执行')
}
mainAppState.mode = 'usbCopying'
return ok()
} catch (err) {
if (String(err).includes('BUSY')) return fail(CS_FAIL, '已有任务在执行')
return fail(CS_FAIL, String(err))
}
})
ipcMain.handle('poll:job-start', (_e, jobId: string) => {
startJobPoll(jobId)
return ok()
})
ipcMain.handle('poll:job-stop', () => {
stopJobPoll()
return ok()
})
ipcMain.handle('poll:usb-start', () => {
startUsbPoll()
return ok()
})
ipcMain.handle('poll:usb-stop', () => {
stopUsbPoll()
mainAppState.mode = 'ready'
return ok()
})
ipcMain.handle('dialog:open-directory', async () => {
const r = await dialog.showOpenDialog({ properties: ['openDirectory', 'multiSelections'] })
if (r.canceled || !r.filePaths.length) return ok({ paths: [] as string[] })
return ok({ paths: r.filePaths })
})
ipcMain.handle('dialog:open-file', async (_e, filters?: { name: string; extensions: string[] }[]) => {
const r = await dialog.showOpenDialog({
properties: ['openFile'],
filters: filters ?? [{ name: 'Soon', extensions: ['soon'] }]
})
if (r.canceled || !r.filePaths[0]) return ok({ path: '' })
return ok({ path: r.filePaths[0] })
})
ipcMain.handle('fs:path-exists', (_e, paths: string[]) => {
const missing = paths.filter((p) => {
const clean = p.replace(/\\\*\\.\\*$/i, '').replace(/\/\*\.\*$/i, '')
return !fs.existsSync(clean)
})
return ok({ missing })
})
ipcMain.handle('config:get', () =>
ok({
sharedDir: configStore.get('sharedDir'),
templateDir: configStore.get('templateDir'),
skipDllInit: configStore.get('skipDllInit', !app.isPackaged)
})
)
ipcMain.handle('config:set', (_e, patch: Record<string, string>) => {
Object.entries(patch).forEach(([k, v]) => configStore.set(k, v))
return ok(configStore.store)
})
ipcMain.handle('shell:open-path', (_e, target: string) => {
const dir = target || (configStore.get('templateDir') as string)
fs.mkdirSync(dir, { recursive: true })
shell.openPath(dir)
return ok()
})
ipcMain.handle('dll:reject-available', () => ok({ available: isRejectApiAvailable() }))
}
export async function handleBeforeQuit(): Promise<void> {
stopAllPolls()
if (
mainAppState.mode === 'distributing' &&
mainAppState.activeJobId &&
isCancelApiAvailable()
) {
try {
dllAdminJobCancel(mainAppState.activeJobId)
} catch (e) {
log.warn('before-quit cancel', e)
}
}
mainAppState.mode = 'ready'
}
+17
View File
@@ -0,0 +1,17 @@
export type MainWorkflowMode = 'ready' | 'distributing' | 'usbCopying'
export const mainAppState = {
initialized: false,
mode: 'ready' as MainWorkflowMode,
activeJobId: ''
}
export function assertReady(): void {
if (!mainAppState.initialized) throw new Error('NOT_INITIALIZED')
}
export function assertNotBusy(): void {
if (mainAppState.mode === 'distributing' || mainAppState.mode === 'usbCopying') {
throw new Error('BUSY')
}
}
+21
View File
@@ -0,0 +1,21 @@
import Store from 'electron-store'
import { app } from 'electron'
import path from 'path'
interface AppConfig {
sharedDir: string
templateDir: string
/** 开发默认 true:不调用 SAPI_Init,避免无打印机时 DLL 刷错 */
skipDllInit: boolean
}
const defaultShared = path.join(app.getPath('userData'), 'Cardsoon', 'tasks')
export const configStore = new Store<AppConfig>({
name: 'cardsoon-config',
defaults: {
sharedDir: defaultShared,
templateDir: path.join(app.getPath('userData'), 'Cardsoon', 'templates'),
skipDllInit: !app.isPackaged
}
})
+88
View File
@@ -0,0 +1,88 @@
import { app } from 'electron'
import path from 'path'
import fs from 'fs'
import log from 'electron-log'
const RUNTIME_CONFIG_FILES = ['CapSettings.json'] as const
const DEFAULT_PRINT_TASKS = path.join('C:', 'PrintTasks')
const DEFAULT_CAP_SETTINGS = `{
"printerList": [],
"Img": [],
"Text": []
}
`
export function getNativeDir(): string {
if (app.isPackaged) {
return path.join(process.resourcesPath, 'native')
}
return path.join(app.getAppPath(), 'resources', 'native')
}
export function getProcessExecDir(): string {
return path.dirname(process.execPath)
}
function ensureBundledConfig(nativeDir: string): void {
fs.mkdirSync(nativeDir, { recursive: true })
for (const name of RUNTIME_CONFIG_FILES) {
const filePath = path.join(nativeDir, name)
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, DEFAULT_CAP_SETTINGS, 'utf8')
log.info(`Created default native config: ${filePath}`)
}
}
}
function deployConfigFile(src: string, destDir: string): void {
const dest = path.join(destDir, path.basename(src))
try {
fs.mkdirSync(destDir, { recursive: true })
if (!fs.existsSync(dest)) {
fs.copyFileSync(src, dest)
log.debug(`Deployed ${path.basename(src)} -> ${dest}`)
return
}
const srcStat = fs.statSync(src)
const destStat = fs.statSync(dest)
if (srcStat.mtimeMs > destStat.mtimeMs) {
fs.copyFileSync(src, dest)
log.debug(`Updated ${path.basename(src)} -> ${dest}`)
}
} catch (e) {
log.warn(`Deploy ${path.basename(src)} to ${destDir} failed`, e)
}
}
function deployRuntimeConfigs(nativeDir: string): void {
const deployDirs = new Set<string>([
nativeDir,
getProcessExecDir(),
DEFAULT_PRINT_TASKS,
path.join(app.getPath('userData'), 'Cardsoon', 'runtime')
])
for (const name of RUNTIME_CONFIG_FILES) {
const src = path.join(nativeDir, name)
if (!fs.existsSync(src)) continue
deployDirs.forEach((dir) => deployConfigFile(src, dir))
}
try {
fs.mkdirSync(DEFAULT_PRINT_TASKS, { recursive: true })
} catch (e) {
log.warn(`Cannot create ${DEFAULT_PRINT_TASKS}`, e)
}
}
export function setupNativeWorkingDir(): void {
const nativeDir = getNativeDir()
if (!fs.existsSync(path.join(nativeDir, 'workDll.dll'))) {
throw new Error(`Native DLL directory not found or incomplete: ${nativeDir}`)
}
ensureBundledConfig(nativeDir)
deployRuntimeConfigs(nativeDir)
process.chdir(nativeDir)
log.debug(`Native working directory: ${nativeDir}`)
}
+106
View File
@@ -0,0 +1,106 @@
import { BrowserWindow } from 'electron'
import log from 'electron-log'
import { POLL_INTERVAL_MS } from '../constants'
import { mainAppState } from './app-state'
import { dllGetJobStateById, dllGetUsbCopyState } from './work-dll.service'
let jobTimer: ReturnType<typeof setInterval> | null = null
let usbTimer: ReturnType<typeof setInterval> | null = null
let jobId = ''
let mainWindow: BrowserWindow | null = null
export function setPollMainWindow(win: BrowserWindow): void {
mainWindow = win
}
function send(channel: string, payload: unknown): void {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send(channel, payload)
}
}
export function stopJobPoll(): void {
if (jobTimer) {
clearInterval(jobTimer)
jobTimer = null
}
}
export function stopUsbPoll(): void {
if (usbTimer) {
clearInterval(usbTimer)
usbTimer = null
}
}
export function stopAllPolls(): void {
stopJobPoll()
stopUsbPoll()
}
export function startJobPoll(id: string): void {
stopJobPoll()
jobId = id
jobTimer = setInterval(() => {
try {
const r = dllGetJobStateById(jobId)
const failed = r.jobState === 4
const cancelled = r.jobState === 6
const finished = r.jobState === 100
const terminal = failed || cancelled
send('job:poll-tick', {
jobId,
queryErrorCode: r.queryErrorCode,
jobState: r.jobState,
progress: r.progress,
terminal,
failed,
cancelled,
finished
})
if (r.queryErrorCode !== 0) {
log.warn('GetJobStateById query failed', r.queryErrorCode)
stopJobPoll()
return
}
if (failed || cancelled) {
stopJobPoll()
if (cancelled) mainAppState.mode = 'ready'
}
} catch (e) {
log.error('job poll error', e)
stopJobPoll()
}
}, POLL_INTERVAL_MS)
}
export function startUsbPoll(): void {
stopUsbPoll()
usbTimer = setInterval(() => {
try {
const r = dllGetUsbCopyState()
const failed = r.taskStatus === 3
const success = r.taskStatus === 2
const terminal = failed || success
send('usb:poll-tick', {
taskStatus: r.taskStatus,
progress: r.progress,
terminal,
failed,
success
})
if (terminal) {
stopUsbPoll()
mainAppState.mode = 'ready'
}
} catch (e) {
log.error('usb poll error', e)
stopUsbPoll()
mainAppState.mode = 'ready'
}
}, POLL_INTERVAL_MS)
}
export function getActiveJobId(): string {
return jobId
}
+182
View File
@@ -0,0 +1,182 @@
import path from 'path'
import koffi from 'koffi'
import log from 'electron-log'
import { CS_OK, JOB_ID_BUF_SIZE, LOG_FATAL_FLAG } from '../constants'
import { getNativeDir } from './native-path'
export interface InitParams {
sharedDir: string
keepCombinedImage?: boolean
stopOnFailure?: boolean
cleanTaskFile?: boolean
autoRetryTimes?: number
rejectConfig?: boolean
logLevel?: number
outBack?: boolean
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let lib: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_Init: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_GetPrinterInfo: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_GetPrinterErrorStr: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_RestJobEx: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_GetJobStateById: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_AdminJobCancel: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_CopyFromUsb: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_GetUsbCopyState: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_PrinterResetprinter: any = null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let SAPI_PrinterMovetoreject: any = null
let hasRejectApi = false
let hasCancelApi = false
let loggedCancelMissing = false
let loggedRejectMissing = false
function loadLibrary(): void {
if (lib) return
const dllPath = path.join(getNativeDir(), 'workDll.dll')
lib = koffi.load(dllPath)
SAPI_Init = lib.func('int __stdcall SAPI_Init(str, bool, bool, bool, int, bool, int, bool)')
SAPI_GetPrinterInfo = lib.func('int __stdcall SAPI_GetPrinterInfo(_Out_ void **)')
SAPI_GetPrinterErrorStr = lib.func('str __stdcall SAPI_GetPrinterErrorStr(int)')
SAPI_RestJobEx = lib.func('int __stdcall SAPI_RestJobEx(str, _Out_ char *, int)')
SAPI_GetJobStateById = lib.func(
'int __stdcall SAPI_GetJobStateById(str, _Out_ int *, _Out_ int *)'
)
SAPI_CopyFromUsb = lib.func('int __stdcall SAPI_CopyFromUsb(str, int)')
SAPI_GetUsbCopyState = lib.func('int __stdcall SAPI_GetUsbCopyState(_Out_ int *, _Out_ int *)')
SAPI_PrinterResetprinter = lib.func('int __stdcall SAPI_PrinterResetprinter()')
try {
SAPI_AdminJobCancel = lib.func('int __stdcall SAPI_AdminJobCancel(str)')
hasCancelApi = true
} catch {
SAPI_AdminJobCancel = null
hasCancelApi = false
if (!loggedCancelMissing) {
loggedCancelMissing = true
log.info('SAPI_AdminJobCancel not in workDll (optional); stop uses poll-stop only')
}
}
try {
SAPI_PrinterMovetoreject = lib.func('int __stdcall SAPI_PrinterMovetoreject()')
hasRejectApi = true
} catch {
hasRejectApi = false
if (!loggedRejectMissing) {
loggedRejectMissing = true
log.info('SAPI_PrinterMovetoreject not in workDll (optional); reject card disabled')
}
}
}
export function isRejectApiAvailable(): boolean {
loadLibrary()
return hasRejectApi
}
export function isCancelApiAvailable(): boolean {
loadLibrary()
return hasCancelApi
}
export function dllInit(params: InitParams): number {
loadLibrary()
return SAPI_Init!(
params.sharedDir,
params.keepCombinedImage ?? true,
params.stopOnFailure ?? false,
params.cleanTaskFile ?? true,
params.autoRetryTimes ?? 0,
params.rejectConfig ?? true,
params.logLevel ?? LOG_FATAL_FLAG,
params.outBack ?? false
) as number
}
export function dllGetPrinterInfo(): { code: number; json?: Record<string, unknown> } {
loadLibrary()
const outPtr = koffi.alloc('void *', 8)
try {
const len = SAPI_GetPrinterInfo!(outPtr) as number
if (len <= 0) return { code: len }
const ptr = koffi.decode(outPtr, 0, 'void *') as number
const jsonStr = koffi.decode(ptr, 'char', len) as string
koffi.free(ptr)
return { code: len, json: JSON.parse(jsonStr) as Record<string, unknown> }
} finally {
koffi.free(outPtr)
}
}
export function dllGetPrinterErrorStr(errorNo = -1): string {
loadLibrary()
const s = SAPI_GetPrinterErrorStr!(errorNo) as string
return s || ''
}
export function dllRestJobEx(json: string): { code: number; jobId: string } {
loadLibrary()
const buf = Buffer.alloc(JOB_ID_BUF_SIZE)
const code = SAPI_RestJobEx!(json, buf, JOB_ID_BUF_SIZE) as number
const jobId = buf.toString('utf8').replace(/\0.*$/, '').trim()
return { code, jobId }
}
export function dllGetJobStateById(jobId: string): {
queryErrorCode: number
jobState: number
progress: number
} {
loadLibrary()
const jobState = [0]
const copyScheduler = [0]
const queryErrorCode = SAPI_GetJobStateById!(jobId, jobState, copyScheduler) as number
return {
queryErrorCode,
jobState: jobState[0],
progress: copyScheduler[0]
}
}
export function dllAdminJobCancel(jobId: string): number {
loadLibrary()
if (!SAPI_AdminJobCancel) throw new Error('CANCEL_API_UNAVAILABLE')
return SAPI_AdminJobCancel(jobId) as number
}
export function dllCopyFromUsb(destFolder: string, cardOutput: number): number {
loadLibrary()
return SAPI_CopyFromUsb!(destFolder, cardOutput) as number
}
export function dllGetUsbCopyState(): { taskStatus: number; progress: number } {
loadLibrary()
const taskStatus = [0]
const progress = [0]
SAPI_GetUsbCopyState!(taskStatus, progress)
return { taskStatus: taskStatus[0], progress: progress[0] }
}
export function dllPrinterReset(): number {
loadLibrary()
return SAPI_PrinterResetprinter!() as number
}
export function dllPrinterReject(): number {
loadLibrary()
if (!SAPI_PrinterMovetoreject) throw new Error('REJECT_API_UNAVAILABLE')
return SAPI_PrinterMovetoreject() as number
}
+28
View File
@@ -0,0 +1,28 @@
import { app } from 'electron'
const SUPPRESS_PATTERNS = [
'Card Printer not detected',
'PrinterAdaptor.cpp',
'ServerAPI.cpp:654',
'using time over',
'Fail to read template file'
]
function shouldSuppress(chunk: string | Uint8Array): boolean {
const text = typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8')
return SUPPRESS_PATTERNS.some((p) => text.includes(p))
}
export function suppressKnownDllStderr(): void {
if (app.isPackaged) return
const stderr = process.stderr
const original = stderr.write.bind(stderr)
stderr.write = ((chunk: string | Uint8Array, ...args: unknown[]) => {
if (shouldSuppress(chunk)) {
return true
}
return (original as (...a: unknown[]) => boolean)(chunk, ...args)
}) as typeof stderr.write
}