diff --git a/PLAN.md b/PLAN.md index 8cf22e7..41658be 100644 --- a/PLAN.md +++ b/PLAN.md @@ -6,10 +6,11 @@ - Drivers will require an overhaul to no longer need handles. - More drivers. - Move more modules to core. + - Wrap some Electron APIs as services for easier mocking without electron itself. - Drivers - Shinybow - Monoprice Blackbird - - J-Tech Digital - - ASHATA - - TESmart - - No Hassle AV + - J-Tech Digital -- Need to find actual command list. + - ASHATA -- Now unable to find. + - TESmart -- Brand of Tesla Elec (like TelsaSmart) + - No Hassle AV -- Need to contact. diff --git a/src/main/drivers/extron/sis.ts b/src/main/drivers/extron/sis.ts index 2c7fae5..5d2fa34 100644 --- a/src/main/drivers/extron/sis.ts +++ b/src/main/drivers/extron/sis.ts @@ -1,9 +1,9 @@ import Logger from 'electron-log' -import { defineDriver, kDeviceCanDecoupleAudioOutput, kDeviceSupportsMultipleOutputs } from '../../services/driver' +import { defineDriver, kDeviceCanDecoupleAudioOutput, kDeviceSupportsMultipleOutputs } from '../../services/drivers' import { createCommandStream } from '../../services/stream' const extronSisDriver = defineDriver({ - enable: true, + enabled: true, guid: '4C8F2838-C91D-431E-84DD-3666D14A6E2C', localized: { en: { @@ -13,8 +13,8 @@ const extronSisDriver = defineDriver({ } }, capabilities: kDeviceSupportsMultipleOutputs | kDeviceCanDecoupleAudioOutput, - setup: async function setup(uri) { - async function sendCommand(command: string) { + setup: () => { + async function sendCommand(uri: string, command: string) { const connection = await createCommandStream(uri, { baudRate: 9600, dataBits: 8, @@ -36,11 +36,11 @@ const extronSisDriver = defineDriver({ await connection.close() } - async function activate(inputChannel: number, videoOutputChannel: number, audioOutputChannel: number) { - Logger.log(`extronSisDriver.activate(${inputChannel}, ${videoOutputChannel}, ${audioOutputChannel})`) - const videoCommand = `${inputChannel}*${videoOutputChannel}%` - const audioCommand = `${inputChannel}*${audioOutputChannel}$` - await sendCommand(`${videoCommand}\r\n${audioCommand}\r\n`) + async function activate(uri: string, input: number, videoOutput: number, audioOutput: number) { + Logger.log(`extronSisDriver.activate(${input}, ${videoOutput}, ${audioOutput})`) + const videoCommand = `${input}*${videoOutput}%` + const audioCommand = `${input}*${audioOutput}$` + await sendCommand(uri, `${videoCommand}\r\n${audioCommand}\r\n`) } async function powerOn() { @@ -55,11 +55,11 @@ const extronSisDriver = defineDriver({ await Promise.resolve() } - return await Promise.resolve({ + return { activate, powerOn, powerOff - }) + } } }) diff --git a/src/main/drivers/sony/rs485.ts b/src/main/drivers/sony/rs485.ts index 4f81975..ff8970c 100644 --- a/src/main/drivers/sony/rs485.ts +++ b/src/main/drivers/sony/rs485.ts @@ -1,11 +1,18 @@ import Logger from 'electron-log' -import { defineDriver, kDeviceHasNoExtraCapabilities } from '../../services/driver' -import { createAddress, createCommand, kAddressAll, kPowerOff, kPowerOn, kSetChannel } from '../../services/sonyRs485' +import { defineDriver, kDeviceHasNoExtraCapabilities } from '../../services/drivers' import { createCommandStream } from '../../services/stream' -import type { Command, CommandArg } from '../../services/sonyRs485' +import { + createAddress, + createCommand, + kAddressAll, + kPowerOff, + kPowerOn, + kSetChannel +} from '../../services/support/sonyRs485' +import type { Command, CommandArg } from '../../services/support/sonyRs485' const sonyRs485Driver = defineDriver({ - enable: true, + enabled: true, guid: '8626D6D3-C211-4D21-B5CC-F5E3B50D9FF0', localized: { en: { @@ -15,8 +22,8 @@ const sonyRs485Driver = defineDriver({ } }, capabilities: kDeviceHasNoExtraCapabilities, - setup: async function setup(uri) { - async function sendCommand(command: Command, arg0?: CommandArg, arg1?: CommandArg) { + setup: () => { + async function sendCommand(uri: string, command: Command, arg0?: CommandArg, arg1?: CommandArg) { const source = createAddress(kAddressAll, 0) const destination = createAddress(kAddressAll, 0) const packet = createCommand(destination, source, command, arg0, arg1) @@ -42,26 +49,26 @@ const sonyRs485Driver = defineDriver({ await connection.close() } - async function activate(inputChannel: number) { - Logger.log(`sonyRs485Driver.activate(${inputChannel})`) - await sendCommand(kSetChannel, 1, inputChannel) + async function activate(uri: string, input: number) { + Logger.log(`sonyRs485Driver.activate(${input})`) + await sendCommand(uri, kSetChannel, 1, input) } - async function powerOn() { + async function powerOn(uri: string) { Logger.log('sonyRs485Driver.powerOn') - await sendCommand(kPowerOn) + await sendCommand(uri, kPowerOn) } - async function powerOff() { + async function powerOff(uri: string) { Logger.log('sonyRs485Driver.powerOff') - await sendCommand(kPowerOff) + await sendCommand(uri, kPowerOff) } - return await Promise.resolve({ + return { activate, powerOn, powerOff - }) + } } }) diff --git a/src/main/drivers/tesla-smart/kvm.ts b/src/main/drivers/tesla-smart/kvm.ts index 56b1413..ccf874d 100644 --- a/src/main/drivers/tesla-smart/kvm.ts +++ b/src/main/drivers/tesla-smart/kvm.ts @@ -1,9 +1,9 @@ import Logger from 'electron-log' -import { defineDriver, kDeviceHasNoExtraCapabilities } from '../../services/driver' +import { defineDriver, kDeviceHasNoExtraCapabilities } from '../../services/drivers' import { createCommandStream } from '../../services/stream' const teslaSmartKvmDriver = defineDriver({ - enable: true, + enabled: true, guid: '91D5BC95-A8E2-4F58-BCAC-A77BA1054D61', localized: { en: { @@ -13,8 +13,8 @@ const teslaSmartKvmDriver = defineDriver({ } }, capabilities: kDeviceHasNoExtraCapabilities, - setup: async function setup(uri) { - async function sendCommand(command: Buffer) { + setup: () => { + async function sendCommand(uri: string, command: Buffer) { const connection = await createCommandStream(uri, { baudRate: 9600, dataBits: 8, @@ -36,9 +36,9 @@ const teslaSmartKvmDriver = defineDriver({ await connection.close() } - async function activate(inputChannel: number) { - Logger.log(`teslaSmartKvmDriver.activate(${inputChannel})`) - await sendCommand(Buffer.of(0xaa, 0xbb, 0x03, 0x01, inputChannel, 0xee)) + async function activate(uri: string, input: number) { + Logger.log(`teslaSmartKvmDriver.activate(${input})`) + await sendCommand(uri, Buffer.of(0xaa, 0xbb, 0x03, 0x01, input, 0xee)) } async function powerOn() { @@ -53,11 +53,11 @@ const teslaSmartKvmDriver = defineDriver({ await Promise.resolve() } - return await Promise.resolve({ + return { activate, powerOn, powerOff - }) + } } }) diff --git a/src/main/drivers/tesla-smart/matrix.ts b/src/main/drivers/tesla-smart/matrix.ts index a9369db..47eda42 100644 --- a/src/main/drivers/tesla-smart/matrix.ts +++ b/src/main/drivers/tesla-smart/matrix.ts @@ -1,9 +1,9 @@ import Logger from 'electron-log' -import { defineDriver, kDeviceSupportsMultipleOutputs } from '../../services/driver' +import { defineDriver, kDeviceSupportsMultipleOutputs } from '../../services/drivers' import { createCommandStream } from '../../services/stream' const teslaSmartMatrixDriver = defineDriver({ - enable: true, + enabled: true, guid: '671824ED-0BC4-43A6-85CC-4877890A7722', localized: { en: { @@ -13,8 +13,8 @@ const teslaSmartMatrixDriver = defineDriver({ } }, capabilities: kDeviceSupportsMultipleOutputs, - setup: async function setup(uri) { - const sendCommand = async (command: Buffer) => { + setup: () => { + const sendCommand = async (uri: string, command: Buffer) => { const connection = await createCommandStream(uri, { baudRate: 9600, dataBits: 8, @@ -38,10 +38,10 @@ const teslaSmartMatrixDriver = defineDriver({ const toChannel = (n: number) => String(n).padStart(2, '0') - async function activate(inputChannel: number, outputChannel: number) { - Logger.log(`teslaSmartMatrixDriver.activate(${inputChannel}, ${outputChannel})`) - const command = `MT00SW${toChannel(inputChannel)}${toChannel(outputChannel)}NT` - await sendCommand(Buffer.from(command, 'ascii')) + async function activate(uri: string, input: number, output: number) { + Logger.log(`teslaSmartMatrixDriver.activate(${input}, ${output})`) + const command = `MT00SW${toChannel(input)}${toChannel(output)}NT` + await sendCommand(uri, Buffer.from(command, 'ascii')) await Promise.resolve() } @@ -58,11 +58,11 @@ const teslaSmartMatrixDriver = defineDriver({ await Promise.resolve() } - return await Promise.resolve({ + return { activate, powerOn, powerOff - }) + } } }) diff --git a/src/main/drivers/tesla-smart/sdi.ts b/src/main/drivers/tesla-smart/sdi.ts index 38bc5fd..4b66e7b 100644 --- a/src/main/drivers/tesla-smart/sdi.ts +++ b/src/main/drivers/tesla-smart/sdi.ts @@ -1,9 +1,9 @@ import Logger from 'electron-log' -import { defineDriver, kDeviceHasNoExtraCapabilities } from '../../services/driver' +import { defineDriver, kDeviceHasNoExtraCapabilities } from '../../services/drivers' import { createCommandStream } from '../../services/stream' const teslaSmartSdiDriver = defineDriver({ - enable: true, + enabled: true, guid: 'DDB13CBC-ABFC-405E-9EA6-4A999F9A16BD', localized: { en: { @@ -13,8 +13,8 @@ const teslaSmartSdiDriver = defineDriver({ } }, capabilities: kDeviceHasNoExtraCapabilities, - setup: async function setup(uri) { - async function sendCommand(command: Buffer) { + setup: () => { + async function sendCommand(uri: string, command: Buffer) { const connection = await createCommandStream(uri, { baudRate: 9600, dataBits: 8, @@ -36,9 +36,9 @@ const teslaSmartSdiDriver = defineDriver({ await connection.close() } - async function activate(inputChannel: number) { - Logger.log(`teslaSmartSdiDriver.activate(${inputChannel})`) - await sendCommand(Buffer.of(0xaa, 0xcc, 0x01, inputChannel)) + async function activate(uri: string, input: number) { + Logger.log(`teslaSmartSdiDriver.activate(${input})`) + await sendCommand(uri, Buffer.of(0xaa, 0xcc, 0x01, input)) } async function powerOn() { @@ -53,11 +53,11 @@ const teslaSmartSdiDriver = defineDriver({ await Promise.resolve() } - return await Promise.resolve({ + return { activate, powerOn, powerOff - }) + } } }) diff --git a/src/main/main.ts b/src/main/main.ts index b244c17..ac1637e 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -5,11 +5,8 @@ import { app, shell, BrowserWindow, nativeTheme } from 'electron' import Logger from 'electron-log' import appIcon from '../../resources/icon.png?asset&asarUnpack' import useAppConfig from './info/config' -import registerDrivers from './plugins/drivers' import useCrypto from './plugins/webcrypto' import useApiServer from './server' -import useDrivers from './services/driver' -import useHandles from './services/handle' import useSystem from './services/system' import useUpdater from './services/updater' import { logError } from './utilities' @@ -98,14 +95,6 @@ app.on('window-all-closed', () => { } }) -// Make sure all handles have been closed. -const { shutDown } = useHandles() -app.on('will-quit', () => { - process.nextTick(async () => { - await shutDown() - }) -}) - // Default open or close DevTools by F12 in development // and ignore CommandOrControl + R in production. // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils @@ -144,7 +133,5 @@ useApiServer() useCrypto() useUpdater() useSystem() -useDrivers() -registerDrivers() await createWindow() diff --git a/src/main/plugins/drivers.ts b/src/main/plugins/drivers.ts deleted file mode 100644 index 782db22..0000000 --- a/src/main/plugins/drivers.ts +++ /dev/null @@ -1,16 +0,0 @@ -import extronSisDriver from '../drivers/extron/sis' -import sonyRs485Driver from '../drivers/sony/rs485' -import teslaSmartKvmDriver from '../drivers/tesla-smart/kvm' -import teslaSmartMatrixDriver from '../drivers/tesla-smart/matrix' -import teslaSmartSdiDriver from '../drivers/tesla-smart/sdi' -import useDrivers from '../services/driver' - -export default function registerDrivers() { - const { register } = useDrivers() - - register(extronSisDriver) - register(sonyRs485Driver) - register(teslaSmartMatrixDriver) - register(teslaSmartKvmDriver) - register(teslaSmartSdiDriver) -} diff --git a/src/main/routes/drivers.ts b/src/main/routes/drivers.ts new file mode 100644 index 0000000..ae6fb8f --- /dev/null +++ b/src/main/routes/drivers.ts @@ -0,0 +1,31 @@ +import { memo } from 'radash' +import { z } from 'zod' +import useDrivers from '../services/drivers' +import { procedure, router } from '../services/trpc' + +const Channel = z.number().int().nonnegative().finite() + +const ActivateInputs = z.tuple([z.string().uuid(), z.string().url(), Channel, Channel, Channel]) +const PowerInputs = z.tuple([z.string().uuid(), z.string().url()]) + +const useDriversRouter = memo(function useDriversRoute() { + const drivers = useDrivers() + + return router({ + all: procedure.query(drivers.all), + get: procedure.input(z.string().uuid()).query(async ({ input }) => { + await drivers.get(input) + }), + activate: procedure.input(ActivateInputs).mutation(async ({ input }) => { + await drivers.activate(...input) + }), + powerOn: procedure.input(PowerInputs).mutation(async ({ input }) => { + await drivers.powerOn(...input) + }), + powerOff: procedure.input(PowerInputs).mutation(async ({ input }) => { + await drivers.powerOff(...input) + }) + }) +}) + +export default useDriversRouter diff --git a/src/main/routes/router.ts b/src/main/routes/router.ts index ed86d32..1329e95 100644 --- a/src/main/routes/router.ts +++ b/src/main/routes/router.ts @@ -6,6 +6,7 @@ import useSourcesRouter from './data/sources' import useUserStoreRouter from './data/storage' import useSwitchesRouter from './data/switches' import useTiesRouter from './data/ties' +import useDriversRouter from './drivers' import useSerialPortRouter from './ports' import useStartupRouter from './startup' @@ -17,6 +18,7 @@ export const useAppRouter = memo(() => // Functional service routes ports: useSerialPortRouter(), startup: useStartupRouter(), + drivers: useDriversRouter(), // Data service routes storage: useUserStoreRouter(), ties: useTiesRouter(), diff --git a/src/main/services/driver.ts b/src/main/services/driver.ts deleted file mode 100644 index dce87c0..0000000 --- a/src/main/services/driver.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { ipcMain } from 'electron' -import { memo } from 'radash' -import { ipcHandle, ipcProxy, logError } from '../utilities' -import useHandles from './handle' -import type { HandleKey } from './handle' -import type { DriverData, Handle } from '../../preload/api' - -// -// Device capabilities -// - -/** The device has no extended capabilities. */ -export const kDeviceHasNoExtraCapabilities = 0 -/** The device has multiple output channels. */ -export const kDeviceSupportsMultipleOutputs = 1 -/** The device support sending the audio output to a different channel. */ -export const kDeviceCanDecoupleAudioOutput = 2 - -// -// Driver definition -// - -/** Interacts with a device. */ -export interface DriverBindings { - /** - * Activates input and output ties. - * - * @param inputChannel - The input channel to tie. - * @param videoOutputChannel - The output video channel to tie. - * @param audioOutputChannel - The output audio channel to tie. - */ - readonly activate: (inputChannel: number, videoOutputChannel: number, audioOutputChannel: number) => Promise - - /** Powers on the switch or monitor. */ - readonly powerOn?: () => Promise - - /** Powers off the switch or monitor. */ - readonly powerOff?: () => Promise - - /** Closes the driver to free any used resources. */ - readonly close?: () => Promise -} - -/** Driver to interact with switching devices. */ -export interface Driver extends DriverBindings { - /** The URI to the device being driven. */ - readonly uri: string -} - -/** Provides the basic information to create a driver factory registration. */ -export interface DriverFactory { - /** Provides information about the driver. */ - readonly data: DriverData - /** Loads a driver. */ - readonly load: (uri: string) => Promise -} - -/** - * Initializes a driver. - * - * @param uri A uri to the device. - */ -export type DriverSetup = (uri: string) => Promise - -export interface DriverOptions extends DriverData { - setup: DriverSetup -} - -/** Defines a driver. */ -export function defineDriver(options: DriverOptions): DriverFactory | undefined { - const { setup, ...data } = options - if (!data.enable) { - return undefined - } - - return Object.freeze({ - data, - load: async (uri: string): Promise => - Object.freeze({ - ...(await setup(uri)), - uri - }) - }) -} - -// -// Driver API back-end -// - -interface DriverBackEnd { - register: (factory: DriverFactory | undefined) => void -} - -const useDrivers = memo(function useDrivers() { - const { createHandle, openHandle } = useHandles() - const kDriverHandle = Symbol.for('@driver') as HandleKey - - /** The driver registry. */ - const registry = new Map() - - /** Registers a driver. */ - function register(factory: DriverFactory | undefined) { - if (factory != null) { - registry.set(factory.data.guid, factory) - } - } - - /** Lists available drivers. */ - async function list() { - return await Promise.resolve(Array.from(registry.values()).map((d) => d.data)) - } - - /** Loads a driver registered in the registry. */ - const open = ipcHandle(async function open(event, guid: string, path: string) { - const factory = registry.get(guid) - if (factory == null) { - throw logError(new Error(`No such driver registered as "${guid}"`)) - } - - return createHandle(event, kDriverHandle, await factory.load(path), async (driver) => { - await driver.close?.() - }) - }) - - const powerOn = ipcHandle(async (event, h: Handle) => { - await openHandle(event, kDriverHandle, h).powerOn?.() - }) - - const powerOff = ipcHandle(async (event, h: Handle) => { - await openHandle(event, kDriverHandle, h).powerOff?.() - }) - - const activate = ipcHandle( - async (event, h: Handle, inputChannel: number, videoOutputChannel: number, audioOutputChannel: number) => { - await openHandle(event, kDriverHandle, h).activate(inputChannel, videoOutputChannel, audioOutputChannel) - } - ) - - ipcMain.handle('driver:list', ipcProxy(list)) - ipcMain.handle('driver:open', open) - ipcMain.handle('driver:powerOn', powerOn) - ipcMain.handle('driver:powerOff', powerOff) - ipcMain.handle('driver:activate', activate) - - return { register } satisfies DriverBackEnd -}) - -export default useDrivers diff --git a/src/main/services/drivers.ts b/src/main/services/drivers.ts new file mode 100644 index 0000000..c97aa17 --- /dev/null +++ b/src/main/services/drivers.ts @@ -0,0 +1,219 @@ +// +// Device capabilities +// + +import { memo } from 'radash' +import type { ApiLocales } from './locale' +import type { MaybePromise } from '@/basics' + +/** The device has no extended capabilities. */ +export type kDeviceHasNoExtraCapabilities = typeof kDeviceHasNoExtraCapabilities +export const kDeviceHasNoExtraCapabilities = 0 +/** The device has multiple output channels. */ +export type kDeviceSupportsMultipleOutputs = typeof kDeviceSupportsMultipleOutputs +export const kDeviceSupportsMultipleOutputs = 1 +/** The device support sending the audio output to a different channel. */ +export type kDeviceCanDecoupleAudioOutput = typeof kDeviceCanDecoupleAudioOutput +export const kDeviceCanDecoupleAudioOutput = 2 + +// +// Driver definition +// + +/** Defines the localized metadata about a driver. */ +export interface LocalizedDriverDescriptor { + /** Defines the title for the driver in a specific locale. */ + readonly title: string + /** Defines the company for the driver in a specific locale. */ + readonly company: string + /** Defines the provider for the driver in a specific locale. */ + readonly provider: string +} + +/** Defines basic metadata about a device and driver. */ +export interface DriverData { + /** + * Indicates whether the driver is enabled, this is to allow partially coded drivers to be + * commited, but not usable to the UI or other code. + */ + readonly enabled: boolean + /** A unique identifier for the driver. */ + readonly guid: string + /** Defines the localized driver information in all supported locales. */ + readonly localized: Readonly> + /** Defines the capabilities of the device driven by the driver. */ + readonly capabilities: number +} + +/** Interacts with a device. */ +export interface DriverBindings { + /** + * Activates input and output ties. + * + * @param uri - URI identifying the location of the device. + * @param inputChannel - The input channel to tie. + * @param videoOutputChannel - The output video channel to tie. + * @param audioOutputChannel - The output audio channel to tie. + */ + readonly activate: ( + uri: string, + inputChannel: number, + videoOutputChannel: number, + audioOutputChannel: number + ) => Promise + + /** + * Powers on the switch or monitor. + * + * @param uri - URI identifying the location of the device. + */ + readonly powerOn?: (uri: string) => Promise + + /** + * Powers off the switch or monitor. + * + * @param uri - URI identifying the location of the device. + */ + readonly powerOff?: (uri: string) => Promise +} + +export interface DefineDriverOptions extends DriverData { + setup: () => DriverBindings +} + +async function noOpPower() { + /* no-op is not defined in setup */ await Promise.resolve() +} + +const registry = new Map() + +export interface Driver { + /** + * Indicates whether the driver is enabled, this is to allow partially coded drivers to be + * commited, but not usable to the UI or other code. + */ + readonly enabled: boolean + /** A unique identifier for the driver. */ + readonly guid: string + /** Defines the capabilities of the device driven by the driver. */ + readonly capabilities: number + /** Raw metadata from the registration options. */ + readonly metadata: DriverData + /** Gets the localized driver information. */ + getInfo: (locale: ApiLocales) => LocalizedDriverDescriptor + /** + * Activates input and output ties. + * + * @param uri - URI identifying the location of the device. + * @param inputChannel - The input channel to tie. + * @param videoOutputChannel - The output video channel to tie. + * @param audioOutputChannel - The output audio channel to tie. + */ + readonly activate: ( + uri: string, + inputChannel: number, + videoOutputChannel: number, + audioOutputChannel: number + ) => Promise + + /** + * Powers on the switch or monitor. + * + * @param uri - URI identifying the location of the device. + */ + readonly powerOn: (uri: string) => Promise + + /** + * Powers off the switch or monitor. + * + * @param uri - URI identifying the location of the device. + */ + readonly powerOff: (uri: string) => Promise +} + +export function defineDriver(options: DefineDriverOptions) { + let existing = registry.get(options.guid) + if (existing != null) return existing + + const { setup, ...info } = options + const implemented = setup() + + const getInfo = memo(function getInfo(locale: ApiLocales) { + const localizedInfo = info.localized[locale] + + return { + enabled: info.enabled, + guid: info.guid, + ...localizedInfo, + capabilities: info.capabilities + } + }) + + existing = Object.freeze({ + powerOn: noOpPower, + powerOff: noOpPower, + ...implemented, + enabled: info.enabled, + guid: info.guid, + capabilities: info.capabilities, + metadata: info, + getInfo + }) + + registry.set(options.guid, existing) + return existing +} + +const useDrivers = memo(function useDriver() { + const drivers = import.meta.glob('../drivers/**/*') + const booted = Promise.all( + Object.values(drivers).map(async (factory) => { + await factory() + }) + ) + + function defineOperation(op: (...args: Args) => MaybePromise) { + return async (...args: Args) => { + await booted + return await op(...args) + } + } + + const all = defineOperation(() => Array.from(registry.values())) + + const get = defineOperation((guid: string) => registry.get(guid) ?? null) + + function defineDriverOperation( + op: (driver: Driver, ...args: Args) => MaybePromise + ) { + return async (guid: string, ...args: Args) => { + const driver = await get(guid) + if (driver == null) throw new ReferenceError(`No such driver: "${guid}"`) + return await op(driver, ...args) + } + } + + const activate = defineDriverOperation( + async (driver, uri: string, input: number, videoOutput: number, audioOutput: number) => { + await driver.activate(uri, input, videoOutput, audioOutput) + } + ) + + const powerOn = defineDriverOperation(async (driver, uri: string) => { + await driver.powerOn(uri) + }) + + const powerOff = defineDriverOperation(async (driver, uri: string) => { + await driver.powerOff(uri) + }) + + return { + all, + get, + activate, + powerOn, + powerOff + } +}) + +export default useDrivers diff --git a/src/main/services/handle.ts b/src/main/services/handle.ts deleted file mode 100644 index bf73d10..0000000 --- a/src/main/services/handle.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { ipcMain } from 'electron' -import { memo } from 'radash' -import { ipcHandle, logError } from '../utilities' -import type { Handle } from '../../preload/api' -import type { SymbolKey } from '@/keys' -import type { IpcMainInvokeEvent, WebContents } from 'electron' -import { warnPromiseFailures } from '@/error-handling' - -export type HandleKey = SymbolKey<'handle', T> - -/** Close routine for a handle. */ -export type Close = (resource: T) => Promise - -/** Handle transparent structure. */ -export interface Descriptor { - key: HandleKey - resource: T - close: Close -} - -async function dummyClose() { - await Promise.resolve() -} - -/** - * Allows the use of an opaque handles for referencing - * resources by the renderer process. - */ -const useHandles = memo(function useHandles() { - /** The maximum number of handles sans 256. */ - const kMaxHandles = 131072 - - /** The NIL/NULL handle. */ - const kNullHandle = 0 as Handle - - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Prevent need to cast - type AnyDescriptor = Descriptor - - // The first 256 will be reserved, and used as a - // sanity check for validity. - const handleMap = new Array(kMaxHandles) - let nextFree = 0x100 - - // Initial the map, ensuring all new element - // point to their neighbor as the next free. - // The final entry will point to one past - // the end of the map, which will mean - // it has been exhausted. - for (let i = 0; i !== kMaxHandles; ++i) { - handleMap[i] = i + 1 - } - - /** - * Determines whether a handle is valid; and optionally, of a given type. - * @param handle The handle to check. - * @param key The key to confirm the type of the handle. - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function isValidHandle(handle: Handle, key?: HandleKey) { - // Get the handle descriptor - // and check its validity. - const descriptor = handleMap[handle] - if (descriptor == null || typeof descriptor !== 'object') { - return false - } - - return key != null ? key === descriptor.key : true - } - - /** - * Gets the descriptor of a handle. - * @param handle The handle from which to get a descriptor. - */ - function getDescriptor(handle: Handle): AnyDescriptor - /** - * Gets the descriptor of a handle and confirms its type. - * @param handle The handle from which to get a descriptor. - * @param key The key to confirm the handle type. - */ - function getDescriptor(handle: Handle, key: HandleKey): Descriptor - /** Implementation */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function getDescriptor(handle: Handle, key?: HandleKey) { - // Get the handle descriptor - // and check its validity. - const descriptor = handleMap[handle] - if (descriptor == null || typeof descriptor !== 'object') { - throw logError(new ReferenceError('Invalid handle')) - } - - if (key == null) { - return descriptor - } - - if (descriptor.key !== key) { - throw logError(new TypeError('Wrong handle')) - } - - return descriptor - } - - const handleTrackers = new WeakMap>() - - function getHandleTracker(sender: WebContents) { - let handleTracker = handleTrackers.get(sender) - if (handleTracker != null) return handleTracker - handleTracker = new Set() - handleTrackers.set(sender, handleTracker) - return handleTracker - } - - /** - * Creates a new handle, with the given type key, resource, and clean up routine. - * @param event - The event from which the handle is being opened. - * @param key - A key used to identify and validate the type of the handle. - * @param resource - A resource that will be attached to the handle. - * @param close - A callback to clean up the resource attached to the handle when it closes. - */ - function createHandle(event: IpcMainInvokeEvent, key: HandleKey, resource: T, close?: Close) { - const { sender } = event - - // Get the handle value and ensure - // there is no handle exhaustion. - const handle = nextFree - if (handle === kMaxHandles) { - throw logError(new Error('Out of handles')) - } - - // Ensure the handle is not already in use, - // indicating a possible map corruption. - const opaque = handleMap[handle] - if (typeof opaque !== 'number') { - throw logError(new Error(`'Handle map corrupt: tNF!=N' ${handle}`)) - } - - // Record the next free and put the - // descriptor in the map so the - // handle can be closed. - nextFree = opaque - handleMap[handle] = { key, resource, close: close ?? dummyClose } - - // Add the handle to the tracker tree. - getHandleTracker(sender).add(handle as Handle) - - return handle as Handle - } - - /** - * Opens a handle to access its attached resource. - * @param key - The handle key that tags the handle type. - * @param handle - The handle to open. - */ - function openHandle(event: IpcMainInvokeEvent, key: HandleKey, handle: Handle) { - const { sender } = event - - // Check that the handle belongs to this sender and frame. - const handles = getHandleTracker(sender) - if (!handles.has(handle)) { - throw logError(new ReferenceError('Invalid handle')) - } - - // Get the handle descriptor - // and check the resource. - const descriptor = getDescriptor(handle, key) - if (descriptor.resource == null) { - throw logError(new ReferenceError('Invalid handle')) - } - - return descriptor.resource // as T - } - - /** - * Closes a handle and cleans up its resource. - * @param event - The event from which the handle is being closed. - * @param handle - The handle to close. - */ - async function freeHandle(event: IpcMainInvokeEvent, handle: Handle) { - const { sender } = event - - // Check that the handle belongs to this sender and frame. - const handles = getHandleTracker(sender) - if (!handles.has(handle)) { - throw logError(new ReferenceError('Invalid handle')) - } - - // Remove the handle from the tracker tree, - // do this ealier so if any other task - // tries to close this handle, it - // cannot see it. - handles.delete(handle) - - // Get the handle descriptor - // and check the resource. - const descriptor = getDescriptor(handle) - if (descriptor.resource == null) { - throw logError(new ReferenceError('Invalid handle')) - } - - // Now, point the next free to the - // handle just closed, and update - // the next free to point to - // said handle. - handleMap[handle] = nextFree - nextFree = handle - - // Ensure that any asynchronous call out are at the end, - // to ensure all changes to the handle map or tracker - // don't interleave. Attempt to close the resource. - // If it fails to close, it must throw an - // error or be silent. - await descriptor.close(descriptor.resource) - } - - /** - * Closes all handles in a frame. - */ - async function freeAllHandle(event: IpcMainInvokeEvent) { - const { sender } = event - - const handles = getHandleTracker(sender) - while (handles.size > 0) { - // Right now, must use a new iterator to always pull the first item. - // This is to attempt to make this as synchronous as possible - // until the actual close, allowing the item to be removed - // before any coroutine pauses for this task cycle. - const next = handles.values().next() - if (next.done === true) return - - // eslint-disable-next-line no-await-in-loop -- Must be serialized to prevent issues. - await freeHandle(event, next.value) - } - } - - /** - * Closes all handles on shutdown. - * - * There is no need to handle the free-list or ownership. - */ - async function shutDown() { - warnPromiseFailures( - 'closing handle failure', - await Promise.allSettled( - handleMap.map(async function closeHandle(handle) { - if (typeof handle === 'object') { - await handle.close(handle.resource) - } else { - await Promise.resolve() - } - }) - ) - ) - } - - ipcMain.handle('handle:free', ipcHandle(freeHandle)) - ipcMain.handle('handle:clean', ipcHandle(freeAllHandle)) - - return { - kNullHandle, - isValidHandle, - createHandle, - openHandle, - freeHandle, - freeAllHandle, - shutDown - } -}) - -export default useHandles diff --git a/src/main/services/level.js b/src/main/services/level.js index c6efdc7..83df748 100644 --- a/src/main/services/level.js +++ b/src/main/services/level.js @@ -25,8 +25,7 @@ export const useLevelDb = memo(function useLevelDb() { function leveldown(name) { const path = resolvePath(app.getPath('userData'), name) const db = levelDown(path) - - app.on('before-quit', () => { + app.on('will-quit', () => { db.close((err) => { if (err != null) console.error(err) }) diff --git a/src/main/services/locale.ts b/src/main/services/locale.ts new file mode 100644 index 0000000..cd01773 --- /dev/null +++ b/src/main/services/locale.ts @@ -0,0 +1,2 @@ +/** Supported API locales. */ +export type ApiLocales = 'en' diff --git a/src/main/services/sonyRs485.ts b/src/main/services/support/sonyRs485.ts similarity index 100% rename from src/main/services/sonyRs485.ts rename to src/main/services/support/sonyRs485.ts diff --git a/src/preload/api.d.ts b/src/preload/api.d.ts index b0dfa82..0882e79 100644 --- a/src/preload/api.d.ts +++ b/src/preload/api.d.ts @@ -12,11 +12,23 @@ export type { UserInfo } from '../main/info/user' export type { AppConfig } from '../main/info/config' export type { AppRouter } from '../main/routes/router' export type { DocumentId } from '../main/services/database' +export type { ApiLocales } from '../main/services/locale' export type { UserStore } from '../main/dao/storage' export type { PortEntry } from '../main/services/ports' export type { Source, NewSource, SourceUpdate } from '../main/dao/sources' export type { Switch, NewSwitch, SwitchUpdate } from '../main/dao/switches' export type { Tie, NewTie, TieUpdate } from '../main/dao/ties' +export type { ApiLocales } from '../main/locale' + +export type { + Driver, + DriverData, + LocalizedDriverDescriptor, + // Cannot be exported as values, but they are literals. + kDeviceCanDecoupleAudioOutput, + kDeviceHasNoExtraCapabilities, + kDeviceSupportsMultipleOutputs +} from '../main/services/drivers' // // Internal parts @@ -41,78 +53,11 @@ export type IpcResponse = IpcReturnedValue | IpcThrownError // Common parts // -/** Supported API locales. */ -export type ApiLocales = 'en' - -/** Opaque handles. */ -export type Handle = Tagged - /** Event listener attachment options */ export interface ListenerOptions { once?: boolean | undefined } -// -// Driver API -// - -/** Defines the localized metadata about a driver. */ -export interface LocalizedDriverDescriptor { - /** Defines the title for the driver in a specific locale. */ - readonly title: string - /** Defines the company for the driver in a specific locale. */ - readonly company: string - /** Defines the provider for the driver in a specific locale. */ - readonly provider: string -} - -/** Defines basic metadata about a device and driver. */ -export interface DriverData { - /** - * Indicates whether the driver is enabled, this is to allow partially coded drivers to be - * commited, but not usable to the UI or other code. - */ - readonly enable: boolean - /** A unique identifier for the driver. */ - readonly guid: string - /** Defines the localized driver information in all supported locales. */ - readonly localized: { - /** Defines the localized driver information in a specific locale. */ - readonly [locale in ApiLocales]: LocalizedDriverDescriptor - } - /** Defines the capabilities of the device driven by the driver. */ - readonly capabilities: number -} - -export interface DriverApi { - readonly capabilities: { - readonly kDeviceHasNoExtraCapabilities: 0 - readonly kDeviceSupportsMultipleOutputs: 1 - readonly kDeviceCanDecoupleAudioOutput: 2 - } - /** Lists registered drivers. */ - readonly list: () => Promise - /** Loads a driver. */ - readonly open: (guid: string, uri: string) => Promise - /** Powers on the switch or monitor. */ - readonly powerOn: (h: Handle) => Promise - /** Closes the device to which the driver is attached. */ - readonly powerOff: (h: Handle) => Promise - /** - * Sets input and output ties. - * - * @param inputChannel The input channel to tie. - * @param videoOutputChannel The output video channel to tie. - * @param audioOutputChannel The output audio channel to tie. - */ - readonly activate: ( - h: Handle, - inputChannel: number, - videoOutputChannel: number, - audioOutputChannel: number - ) => Promise -} - // // Session control API // @@ -155,13 +100,8 @@ export interface ProcessData { /** Functional APIs */ export interface MainProcessServices { readonly process: ProcessData - readonly driver: DriverApi readonly system: SystemApi readonly updates: AppUpdates - /** Closes a handle, freeing its resources. */ - readonly freeHandle: (h: Handle) => Promise - /** Closes all handles for a page. */ - readonly freeAllHandles: () => Promise } export interface AppUpdater { diff --git a/src/preload/index.ts b/src/preload/index.ts index 4385dc9..11ad616 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -10,12 +10,7 @@ if (!process.contextIsolated) { try { // Register services and setup to free all handles when the window closes. - const services = useServices() - globalThis.addEventListener('beforeunload', () => { - services.freeAllHandles().catch((e: unknown) => { - console.error(e) - }) - }) + useServices() useAppConfig() } catch (e) { diff --git a/src/preload/plugins/services.ts b/src/preload/plugins/services.ts index 8ccb297..b47813d 100644 --- a/src/preload/plugins/services.ts +++ b/src/preload/plugins/services.ts @@ -1,22 +1,15 @@ import { contextBridge } from 'electron' import { memo } from 'radash' -import { useIpc } from '../support' -import useDriverApi from './services/driver' import useProcessData from './services/process' import useSystemApi from './services/system' import useAppUpdates from './services/updates' import type { MainProcessServices } from '../api' const useServices = memo(function useServices() { - const ipc = useIpc() - const services = { process: useProcessData(), - driver: useDriverApi(), system: useSystemApi(), - updates: useAppUpdates(), - freeHandle: ipc.useInvoke('handle:free'), - freeAllHandles: ipc.useInvoke('handle:clean') + updates: useAppUpdates() } satisfies MainProcessServices contextBridge.exposeInMainWorld('services', services) diff --git a/src/preload/plugins/services/driver.ts b/src/preload/plugins/services/driver.ts deleted file mode 100644 index 41404a1..0000000 --- a/src/preload/plugins/services/driver.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { memo } from 'radash' -import { useIpc } from '../../support' -import type { DriverApi } from '../../api' - -const useDriverApi = memo(function useDriverApi() { - const ipc = useIpc() - - return { - capabilities: Object.freeze({ - kDeviceHasNoExtraCapabilities: 0, - kDeviceSupportsMultipleOutputs: 1, - kDeviceCanDecoupleAudioOutput: 2 - }), - list: ipc.useInvoke('driver:list'), - open: ipc.useInvoke('driver:open'), - activate: ipc.useInvoke('driver:activate'), - powerOn: ipc.useInvoke('driver:powerOn'), - powerOff: ipc.useInvoke('driver:powerOff') - } satisfies DriverApi -}) - -export default useDriverApi diff --git a/src/renderer/modals/SwitchDialog.vue b/src/renderer/modals/SwitchDialog.vue index 2e47760..809f06d 100644 --- a/src/renderer/modals/SwitchDialog.vue +++ b/src/renderer/modals/SwitchDialog.vue @@ -5,7 +5,7 @@ import { computed, ref, reactive, onBeforeMount } from 'vue' import { useI18n } from 'vue-i18n' import { useLocation } from '../helpers/location' import { useRules, useValidation } from '../helpers/validation' -import { useDrivers } from '../services/driver' +import useDrivers from '../services/driver' import usePorts from '../services/ports' import { useDialogs, useSwitchDialog } from './dialogs' import type { I18nSchema } from '../locales/locales' diff --git a/src/renderer/modals/TieDialog.vue b/src/renderer/modals/TieDialog.vue index 2c45b9e..0e18340 100644 --- a/src/renderer/modals/TieDialog.vue +++ b/src/renderer/modals/TieDialog.vue @@ -5,7 +5,7 @@ import { computed, ref, reactive, watch, onBeforeMount } from 'vue' import { useI18n } from 'vue-i18n' import NumberInput from '../components/NumberInput.vue' import { useRules, useValidation } from '../helpers/validation' -import { kDeviceCanDecoupleAudioOutput, kDeviceSupportsMultipleOutputs, useDrivers } from '../services/driver' +import useDrivers, { kDeviceCanDecoupleAudioOutput, kDeviceSupportsMultipleOutputs } from '../services/driver' import { useSources } from '../services/sources' import { useSwitches } from '../services/switches' import { useDialogs, useTieDialog } from './dialogs' diff --git a/src/renderer/pages/SourcePage.vue b/src/renderer/pages/SourcePage.vue index 9e6c9c5..676e124 100644 --- a/src/renderer/pages/SourcePage.vue +++ b/src/renderer/pages/SourcePage.vue @@ -10,7 +10,7 @@ import { filesToAttachment, toFiles } from '../helpers/attachment' import { useGuardedAsyncOp } from '../helpers/utilities' import TieDialog from '../modals/TieDialog.vue' import { useDialogs, useTieDialog } from '../modals/dialogs' -import { useDrivers } from '../services/driver' +import useDrivers from '../services/driver' import { useSources } from '../services/sources' import { useSwitches } from '../services/switches' import { useTies } from '../services/ties' @@ -113,6 +113,7 @@ const loadTies = useGuardedAsyncOp(async function loadTies() { }) onBeforeMount(loadTies) +onBeforeMount(drivers.all) async function addTie(target: NewTie) { try { diff --git a/src/renderer/pages/SwitchList.vue b/src/renderer/pages/SwitchList.vue index f3b47f2..110d168 100644 --- a/src/renderer/pages/SwitchList.vue +++ b/src/renderer/pages/SwitchList.vue @@ -7,7 +7,7 @@ import Page from '../components/Page.vue' import { useGuardedAsyncOp } from '../helpers/utilities' import SwitchDialog from '../modals/SwitchDialog.vue' import { useDialogs, useSwitchDialog } from '../modals/dialogs' -import { useDrivers } from '../services/driver' +import useDrivers from '../services/driver' import { useSwitches } from '../services/switches' import type { I18nSchema } from '../locales/locales' import type { DriverInformation } from '../services/driver' @@ -40,6 +40,7 @@ const items = computed(() => ) const refresh = useGuardedAsyncOp(async () => { + // TODO: await drivers.all(), with busy tracking await switches.all() }) diff --git a/src/renderer/services/backup/import.ts b/src/renderer/services/backup/import.ts index 2ca7a4c..d0fa602 100644 --- a/src/renderer/services/backup/import.ts +++ b/src/renderer/services/backup/import.ts @@ -2,7 +2,7 @@ import { BlobReader, BlobWriter, TextWriter, ZipReader } from '@zip.js/zip.js' import mime from 'mime' import { z } from 'zod' import { fileToAttachment } from '../../helpers/attachment' -import { useDrivers } from '../driver' +import useDrivers from '../driver' import useSettings from '../settings' import { useSources } from '../sources' import { useSwitches } from '../switches' diff --git a/src/renderer/services/dashboard.ts b/src/renderer/services/dashboard.ts index 4f1530d..cbfd1af 100644 --- a/src/renderer/services/dashboard.ts +++ b/src/renderer/services/dashboard.ts @@ -1,7 +1,7 @@ import { defineStore } from 'pinia' import { computed, readonly, ref } from 'vue' import { toFiles, useImages } from '../helpers/attachment' -import { useDrivers } from './driver' +import useDrivers from './driver' import useSettings from './settings' import { useSources } from './sources' import { useSwitches } from './switches' @@ -58,27 +58,17 @@ export const useDashboard = defineStore('dashboard', function defineDashboard() } } - warnPromiseFailures( - 'driver close failure', - await Promise.allSettled( - closing.map(async (driver) => { - await driver.close() - }) - ) - ) - // Load any drivers that are new or are being replaced. - const loading: Promise<[DocumentId, Driver]>[] = [] + const loading = new Array<[string, Driver]>() for (const switcher of switches.items) { const driver = loadedDrivers.get(switcher._id) if (driver == null || driver.uri !== switcher.path) { - loading.push(drivers.load(switcher.driverId, switcher.path).then((loaded) => [switcher._id, loaded])) + loading.push([switcher._id, drivers.load(switcher.driverId, switcher.path)]) } } // Add the replaced and new driver to the loaded registry. - const loaded = await Promise.all(loading) - for (const [guid, driver] of loaded) { + for (const [guid, driver] of loading) { loadedDrivers.set(guid, driver) } } diff --git a/src/renderer/services/driver.ts b/src/renderer/services/driver.ts index 05ab39a..5228049 100644 --- a/src/renderer/services/driver.ts +++ b/src/renderer/services/driver.ts @@ -1,68 +1,72 @@ -import { createSharedComposable } from '@vueuse/core' -import { readonly, computed, reactive } from 'vue' +import { memo } from 'radash' +import { computed, reactive, readonly, ref, shallowReadonly } from 'vue' import i18n from '../plugins/i18n' +import { useClient } from './rpc' import { trackBusy } from './tracking' +import type { + kDeviceHasNoExtraCapabilities as HasNoExtraCapabilities, + kDeviceSupportsMultipleOutputs as SupportsMultipleOutputs, + kDeviceCanDecoupleAudioOutput as CanDecoupleAudioOutput, + LocalizedDriverDescriptor +} from '../../preload/api' /** The device has no extended capabilities. */ -export const kDeviceHasNoExtraCapabilities = services.driver.capabilities.kDeviceHasNoExtraCapabilities +export type kDeviceHasNoExtraCapabilities = HasNoExtraCapabilities +export const kDeviceHasNoExtraCapabilities: HasNoExtraCapabilities = 0 /** The device has multiple output channels. */ -export const kDeviceSupportsMultipleOutputs = services.driver.capabilities.kDeviceSupportsMultipleOutputs +export type kDeviceSupportsMultipleOutputs = SupportsMultipleOutputs +export const kDeviceSupportsMultipleOutputs: SupportsMultipleOutputs = 1 /** The device support sending the audio output to a different channel. */ -export const kDeviceCanDecoupleAudioOutput = services.driver.capabilities.kDeviceCanDecoupleAudioOutput +export type kDeviceCanDecoupleAudioOutput = CanDecoupleAudioOutput +export const kDeviceCanDecoupleAudioOutput: CanDecoupleAudioOutput = 2 /** Informational metadata about a device and driver. */ -export interface DriverInformation { +export interface DriverInformation extends LocalizedDriverDescriptor { /** A unique identifier for the driver. */ readonly guid: string - /** Defines the title for the driver in a specific locale. */ - readonly title: string - /** Defines the company for the driver in a specific locale. */ - readonly company: string - /** Defines the provider for the driver in a specific locale. */ - readonly provider: string /** Defines the capabilities of the device driven by the driver. */ readonly capabilities: number } /** Driver to interact with switching devices. */ -export interface Driver { +export interface Driver extends DriverInformation { /** * Sets input and output ties. * - * @param inputChannel The input channel to tie. - * @param videoOutputChannel The output video channel to tie. - * @param audioOutputChannel The output audio channel to tie. + * @param input The input channel to tie. + * @param videoOutput The output video channel to tie. + * @param audioOutput The output audio channel to tie. */ - readonly activate: (inputChannel: number, videoOutputChannel: number, audioOutputChannel: number) => Promise + readonly activate: (input: number, videoOutput: number, audioOutput: number) => Promise /** Powers on the switch or monitor. */ readonly powerOn: () => Promise /** Powers on the switch or monitor and closes the driver. */ readonly powerOff: () => Promise - /** Closes the driver. */ - readonly close: () => Promise /** The URI to the device being driven. */ readonly uri: string } -/** Core parts of the drive system, so we don't hold any component's effect scope in memory. */ -const useDriverCore = createSharedComposable(function useDriverCore() { - const registry = reactive(new Map()) +const useDrivers = memo(function useDrivers() { + const client = useClient() - /** Loads the drivers, using the first i18n we can get. */ - async function loadList() { - if (registry.size > 0) { - // Already loaded... - return - } + /** Busy tracking. */ + const tracker = trackBusy() + + /** The registered drivers. */ + const items = ref(new Array()) + + const registry = new Map() - const drivers = await services.driver.list() - for (const { guid, localized, capabilities } of drivers) { + async function all() { + if (items.value.length > 0) return items.value + const drivers = await tracker.wait(client.drivers.all.query()) + for (const { + metadata: { guid, localized, capabilities } + } of drivers) { /** The localized driver information made i18n compatible. */ for (const [locale, description] of Object.entries(localized)) { i18n.global.mergeLocaleMessage(locale as never, { - $driver: { - [guid]: { ...description } - } + $driver: { [guid]: { ...description } } }) } @@ -83,66 +87,49 @@ const useDriverCore = createSharedComposable(function useDriverCore() { }) ) } - } - - return { registry, loadList } -}) -/** Use drivers. */ -export function useDrivers() { - const { registry, loadList } = useDriverCore() - - /** Busy tracking. */ - const tracker = trackBusy() - - /** The registered drivers. */ - const items = computed(() => Array.from(registry.values())) - - /** Loads information about all the drivers. */ - const all = tracker.track(async function all() { - await loadList() - }) + items.value = Array.from(registry.values()) + return items.value + } - /** Loads a driver registered in the registry. */ - async function load(guid: string, path: string): Promise { - await loadList() - if (!registry.has(guid)) { - throw new Error(`No such driver registered as "${guid}"`) + function load(guid: string, uri: string): Driver { + if (items.value.length === 0) { + throw new ReferenceError(`No driver "${guid}"`) } - const h = await services.driver.open(guid, path) + const driver = registry.get(guid) + if (driver == null) { + throw new ReferenceError(`No driver "${guid}"`) + } - async function activate(inputChannel: number, videoOutputChannel: number, audioOutputChannel: number) { - await services.driver.activate(h, inputChannel, videoOutputChannel, audioOutputChannel) + async function activate(input: number, videoOutput: number, audioOutput: number) { + await client.drivers.activate.mutate([guid, uri, input, videoOutput, audioOutput]) } async function powerOn() { - await services.driver.powerOn(h) + await client.drivers.powerOn.mutate([guid, uri]) } async function powerOff() { - await services.driver.powerOff(h) - await services.freeHandle(h) - } - - async function close() { - await services.freeHandle(h) + await client.drivers.powerOff.mutate([guid, uri]) } return readonly({ + ...driver, activate, powerOn, powerOff, - close, - uri: path + uri }) } return reactive({ isBusy: tracker.isBusy, error: tracker.error, - items: computed(() => readonly(items.value)), + items: computed(() => shallowReadonly(items.value)), all, load }) -} +}) + +export default useDrivers diff --git a/src/tests/drivers/extron/sis.test.ts b/src/tests/drivers/extron/sis.test.ts index 41bc564..3a0769b 100644 --- a/src/tests/drivers/extron/sis.test.ts +++ b/src/tests/drivers/extron/sis.test.ts @@ -6,63 +6,28 @@ const port = await vi.hoisted(async () => await import('../../support/serial')) const stream = await vi.hoisted(async () => await import('../../support/stream')) beforeEach(async (context) => { - vi.mock(import('electron'), mock.electronModule) vi.mock(import('electron-log')) vi.mock(import('serialport'), port.serialPortModule) vi.doMock(import('../../../main/services/stream'), stream.commandStreamModule(context)) - await mock.bridgeCmdrBasics() await port.createMockPorts() - const { default: useDrivers } = await import('../../../main/services/driver') - const { default: registerDrivers } = await import('../../../main/plugins/drivers') - useDrivers() - registerDrivers() }) afterEach(async () => { - await globalThis.services.freeAllHandles() await port.resetMockPorts() + vi.restoreAllMocks() vi.resetModules() }) const kDriverGuid = '4C8F2838-C91D-431E-84DD-3666D14A6E2C' test('available', async () => { - const { kDeviceSupportsMultipleOutputs, kDeviceCanDecoupleAudioOutput } = await import( - '../../../main/services/driver' - ) - - // Raw list. - await expect(globalThis.services.driver.list()).resolves.toContainEqual({ - enable: true, - guid: kDriverGuid, - localized: { - en: { - title: 'Extron SIS-compatible matrix switch', - company: 'Extron Electronics', - provider: 'BridgeCmdr contributors' - } - }, - capabilities: kDeviceSupportsMultipleOutputs | kDeviceCanDecoupleAudioOutput - }) - - // Localized list. - const { useDrivers } = await import('../../../renderer/services/driver') - const drivers = useDrivers() - await expect(drivers.all()).resolves.toBeUndefined() - expect(drivers.items).toContainEqual({ - guid: kDriverGuid, - title: 'Extron SIS-compatible matrix switch', - company: 'Extron Electronics', - provider: 'BridgeCmdr contributors', - capabilities: kDeviceSupportsMultipleOutputs | kDeviceCanDecoupleAudioOutput - }) -}) + const { default: driver } = await import('../../../main/drivers/extron/sis') + const { default: useDrivers } = await import('../../../main/services/drivers') -test('connect', async () => { - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const drivers = useDrivers() - await expect(load(kDriverGuid, 'port:/dev/ttyS0')).resolves.not.toBeNull() + await expect(drivers.all()).resolves.toContainEqual(driver) + await expect(drivers.get(kDriverGuid)).resolves.toEqual(driver) }) test('power on', async (context) => { @@ -70,15 +35,14 @@ test('power on', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() // Power on is a no-op context.stream.withSequence() - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.powerOn()).resolves.toBeUndefined() + await expect(drivers.powerOn(kDriverGuid, 'port:/dev/ttyS0')).resolves.toBeUndefined() context.stream.sequence.expectDone() }) @@ -87,15 +51,14 @@ test('power off', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() // Power off is a no-op context.stream.withSequence() - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.powerOff()).resolves.toBeUndefined() + await expect(drivers.powerOff(kDriverGuid, 'port:/dev/ttyS0')).resolves.toBeUndefined() context.stream.sequence.expectDone() }) @@ -104,8 +67,8 @@ test('activate tie', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() const input = 1 const video = 2 @@ -118,7 +81,6 @@ test('activate tie', async (context) => { context.stream.withSequence().on(Buffer.from(command, 'ascii'), () => Buffer.from(response)) - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.activate(input, video, audio)).resolves.toBeUndefined() + await expect(drivers.activate(kDriverGuid, 'port:/dev/ttyS0', input, video, audio)).resolves.toBeUndefined() context.stream.sequence.expectDone() }) diff --git a/src/tests/drivers/sony/rs485.test.ts b/src/tests/drivers/sony/rs485.test.ts index f2e5419..ba9870c 100644 --- a/src/tests/drivers/sony/rs485.test.ts +++ b/src/tests/drivers/sony/rs485.test.ts @@ -6,61 +6,28 @@ const port = await vi.hoisted(async () => await import('../../support/serial')) const stream = await vi.hoisted(async () => await import('../../support/stream')) beforeEach(async (context) => { - vi.mock(import('electron'), mock.electronModule) vi.mock(import('electron-log')) vi.mock(import('serialport'), port.serialPortModule) vi.doMock(import('../../../main/services/stream'), stream.commandStreamModule(context)) - await mock.bridgeCmdrBasics() await port.createMockPorts() - const { default: useDrivers } = await import('../../../main/services/driver') - const { default: registerDrivers } = await import('../../../main/plugins/drivers') - useDrivers() - registerDrivers() }) afterEach(async () => { - await globalThis.services.freeAllHandles() await port.resetMockPorts() + vi.restoreAllMocks() vi.resetModules() }) const kDriverGuid = '8626D6D3-C211-4D21-B5CC-F5E3B50D9FF0' test('available', async () => { - const { kDeviceHasNoExtraCapabilities } = await import('../../../main/services/driver') - - // Raw list. - await expect(globalThis.services.driver.list()).resolves.toContainEqual({ - enable: true, - guid: kDriverGuid, - localized: { - en: { - title: 'Sony RS-485 controllable monitor', - company: 'Sony Corporation', - provider: 'BridgeCmdr contributors' - } - }, - capabilities: kDeviceHasNoExtraCapabilities - }) - - // Localized list. - const { useDrivers } = await import('../../../renderer/services/driver') - const drivers = useDrivers() - await expect(drivers.all()).resolves.toBeUndefined() - expect(drivers.items).toContainEqual({ - guid: kDriverGuid, - title: 'Sony RS-485 controllable monitor', - company: 'Sony Corporation', - provider: 'BridgeCmdr contributors', - capabilities: kDeviceHasNoExtraCapabilities - }) -}) + const { default: driver } = await import('../../../main/drivers/sony/rs485') + const { default: useDrivers } = await import('../../../main/services/drivers') -test('connect', async () => { - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const drivers = useDrivers() - await expect(load(kDriverGuid, 'port:/dev/ttyS0')).resolves.not.toBeNull() + await expect(drivers.all()).resolves.toContainEqual(driver) + await expect(drivers.get(kDriverGuid)).resolves.toEqual(driver) }) test('power on', async (context) => { @@ -68,8 +35,8 @@ test('power on', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() const command = Buffer.of(0x02, 4, 0xc0, 0xc0, 0x29, 0x3e, 0x15) // Packet type: Command == 0x02 @@ -82,8 +49,7 @@ test('power on', async (context) => { context.stream.withSequence().on(command, () => Buffer.from('Good')) - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.powerOn()).resolves.toBeUndefined() + await expect(drivers.powerOn(kDriverGuid, 'port:/dev/ttyS0')).resolves.toBeUndefined() context.stream.sequence.expectDone() }) @@ -92,8 +58,8 @@ test('power off', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() const commnad = Buffer.of(0x02, 4, 0xc0, 0xc0, 0x2a, 0x3e, 0x14) // Packet type: Command == 0x02 @@ -106,8 +72,7 @@ test('power off', async (context) => { context.stream.withSequence().on(commnad, () => Buffer.from('Good')) - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.powerOff()).resolves.toBeUndefined() + await expect(drivers.powerOff(kDriverGuid, 'port:/dev/ttyS0')).resolves.toBeUndefined() context.stream.sequence.expectDone() }) @@ -116,8 +81,8 @@ test('activate tie', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() const input = 1 const command = Buffer.of(0x02, 6, 0xc0, 0xc0, 0x21, 0x00, input, 0x01, 0x57) @@ -133,7 +98,6 @@ test('activate tie', async (context) => { context.stream.withSequence().on(command, () => Buffer.from('Good')) - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.activate(input, 0, 0)).resolves.toBeUndefined() + await expect(drivers.activate(kDriverGuid, 'port:/dev/ttyS0', input, 0, 0)).resolves.toBeUndefined() context.stream.sequence.expectDone() }) diff --git a/src/tests/drivers/tesla-smart/kvm.test.ts b/src/tests/drivers/tesla-smart/kvm.test.ts index 0c458e9..37336b7 100644 --- a/src/tests/drivers/tesla-smart/kvm.test.ts +++ b/src/tests/drivers/tesla-smart/kvm.test.ts @@ -6,61 +6,28 @@ const port = await vi.hoisted(async () => await import('../../support/serial')) const stream = await vi.hoisted(async () => await import('../../support/stream')) beforeEach(async (context) => { - vi.mock(import('electron'), mock.electronModule) vi.mock(import('electron-log')) vi.mock(import('serialport'), port.serialPortModule) vi.doMock(import('../../../main/services/stream'), stream.commandStreamModule(context)) - await mock.bridgeCmdrBasics() await port.createMockPorts() - const { default: useDrivers } = await import('../../../main/services/driver') - const { default: registerDrivers } = await import('../../../main/plugins/drivers') - useDrivers() - registerDrivers() }) afterEach(async () => { - await globalThis.services.freeAllHandles() await port.resetMockPorts() + vi.restoreAllMocks() vi.resetModules() }) const kDriverGuid = '91D5BC95-A8E2-4F58-BCAC-A77BA1054D61' test('available', async () => { - const { kDeviceHasNoExtraCapabilities } = await import('../../../main/services/driver') - - // Raw list. - await expect(globalThis.services.driver.list()).resolves.toContainEqual({ - enable: true, - guid: kDriverGuid, - localized: { - en: { - title: 'Tesla smart KVM-compatible switch', - company: 'Tesla Elec Technology Co.,Ltd', - provider: 'BridgeCmdr contributors' - } - }, - capabilities: kDeviceHasNoExtraCapabilities - }) - - // Localized list. - const { useDrivers } = await import('../../../renderer/services/driver') - const drivers = useDrivers() - await expect(drivers.all()).resolves.toBeUndefined() - expect(drivers.items).toContainEqual({ - guid: kDriverGuid, - title: 'Tesla smart KVM-compatible switch', - company: 'Tesla Elec Technology Co.,Ltd', - provider: 'BridgeCmdr contributors', - capabilities: kDeviceHasNoExtraCapabilities - }) -}) + const { default: driver } = await import('../../../main/drivers/tesla-smart/kvm') + const { default: useDrivers } = await import('../../../main/services/drivers') -test('connect', async () => { - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const drivers = useDrivers() - await expect(load(kDriverGuid, 'port:/dev/ttyS0')).resolves.not.toBeNull() + await expect(drivers.all()).resolves.toContainEqual(driver) + await expect(drivers.get(kDriverGuid)).resolves.toEqual(driver) }) test('power on', async (context) => { @@ -68,15 +35,14 @@ test('power on', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() // Power on is a no-op context.stream.withSequence() - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.powerOn()).resolves.toBeUndefined() + await expect(drivers.powerOn(kDriverGuid, 'port:/dev/ttyS0')).resolves.toBeUndefined() context.stream.sequence.expectDone() }) @@ -85,15 +51,14 @@ test('power off', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() // Power off is a no-op context.stream.withSequence() - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.powerOff()).resolves.toBeUndefined() + await expect(drivers.powerOff(kDriverGuid, 'port:/dev/ttyS0')).resolves.toBeUndefined() context.stream.sequence.expectDone() }) @@ -102,15 +67,14 @@ test('activate tie', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() const input = 1 const command = Buffer.of(0xaa, 0xbb, 0x03, 0x01, input, 0xee) context.stream.withSequence().on(Buffer.from(command), () => Buffer.from('Good')) - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.activate(input, 0, 0)).resolves.toBeUndefined() + await expect(drivers.activate(kDriverGuid, 'port:/dev/ttyS0', input, 0, 0)).resolves.toBeUndefined() context.stream.sequence.expectDone() }) diff --git a/src/tests/drivers/tesla-smart/matrix.test.ts b/src/tests/drivers/tesla-smart/matrix.test.ts index 888cd5a..413c646 100644 --- a/src/tests/drivers/tesla-smart/matrix.test.ts +++ b/src/tests/drivers/tesla-smart/matrix.test.ts @@ -6,61 +6,28 @@ const port = await vi.hoisted(async () => await import('../../support/serial')) const stream = await vi.hoisted(async () => await import('../../support/stream')) beforeEach(async (context) => { - vi.mock(import('electron'), mock.electronModule) vi.mock(import('electron-log')) vi.mock(import('serialport'), port.serialPortModule) vi.doMock(import('../../../main/services/stream'), stream.commandStreamModule(context)) - await mock.bridgeCmdrBasics() await port.createMockPorts() - const { default: useDrivers } = await import('../../../main/services/driver') - const { default: registerDrivers } = await import('../../../main/plugins/drivers') - useDrivers() - registerDrivers() }) afterEach(async () => { - await globalThis.services.freeAllHandles() await port.resetMockPorts() + vi.restoreAllMocks() vi.resetModules() }) const kDriverGuid = '671824ED-0BC4-43A6-85CC-4877890A7722' test('available', async () => { - const { kDeviceSupportsMultipleOutputs } = await import('../../../main/services/driver') - - // Raw list. - await expect(globalThis.services.driver.list()).resolves.toContainEqual({ - enable: true, - guid: kDriverGuid, - localized: { - en: { - title: 'Tesla smart matrix-compatible switch', - company: 'Tesla Elec Technology Co.,Ltd', - provider: 'BridgeCmdr contributors' - } - }, - capabilities: kDeviceSupportsMultipleOutputs - }) - - // Localized list. - const { useDrivers } = await import('../../../renderer/services/driver') - const drivers = useDrivers() - await expect(drivers.all()).resolves.toBeUndefined() - expect(drivers.items).toContainEqual({ - guid: kDriverGuid, - title: 'Tesla smart matrix-compatible switch', - company: 'Tesla Elec Technology Co.,Ltd', - provider: 'BridgeCmdr contributors', - capabilities: kDeviceSupportsMultipleOutputs - }) -}) + const { default: driver } = await import('../../../main/drivers/tesla-smart/matrix') + const { default: useDrivers } = await import('../../../main/services/drivers') -test('connect', async () => { - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const drivers = useDrivers() - await expect(load(kDriverGuid, 'port:/dev/ttyS0')).resolves.not.toBeNull() + await expect(drivers.all()).resolves.toContainEqual(driver) + await expect(drivers.get(kDriverGuid)).resolves.toEqual(driver) }) test('power on', async (context) => { @@ -68,15 +35,14 @@ test('power on', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() // Power on is a no-op context.stream.withSequence() - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.powerOn()).resolves.toBeUndefined() + await expect(drivers.powerOn(kDriverGuid, 'port:/dev/ttyS0')).resolves.toBeUndefined() context.stream.sequence.expectDone() }) @@ -85,15 +51,14 @@ test('power off', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() // Power off is a no-op context.stream.withSequence() - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.powerOff()).resolves.toBeUndefined() + await expect(drivers.powerOff(kDriverGuid, 'port:/dev/ttyS0')).resolves.toBeUndefined() context.stream.sequence.expectDone() }) @@ -102,8 +67,8 @@ test('activate tie', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() const input = 1 const output = 2 @@ -113,7 +78,6 @@ test('activate tie', async (context) => { context.stream.withSequence().on(Buffer.from(command, 'ascii'), () => Buffer.from('Good')) - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.activate(input, output, 0)).resolves.toBeUndefined() + await expect(drivers.activate(kDriverGuid, 'port:/dev/ttyS0', input, output, 0)).resolves.toBeUndefined() context.stream.sequence.expectDone() }) diff --git a/src/tests/drivers/tesla-smart/sdi.test.ts b/src/tests/drivers/tesla-smart/sdi.test.ts index 8820907..7eac0a7 100644 --- a/src/tests/drivers/tesla-smart/sdi.test.ts +++ b/src/tests/drivers/tesla-smart/sdi.test.ts @@ -6,61 +6,28 @@ const port = await vi.hoisted(async () => await import('../../support/serial')) const stream = await vi.hoisted(async () => await import('../../support/stream')) beforeEach(async (context) => { - vi.mock(import('electron'), mock.electronModule) vi.mock(import('electron-log')) vi.mock(import('serialport'), port.serialPortModule) vi.doMock(import('../../../main/services/stream'), stream.commandStreamModule(context)) - await mock.bridgeCmdrBasics() await port.createMockPorts() - const { default: useDrivers } = await import('../../../main/services/driver') - const { default: registerDrivers } = await import('../../../main/plugins/drivers') - useDrivers() - registerDrivers() }) afterEach(async () => { - await globalThis.services.freeAllHandles() await port.resetMockPorts() + vi.restoreAllMocks() vi.resetModules() }) const kDriverGuid = 'DDB13CBC-ABFC-405E-9EA6-4A999F9A16BD' test('available', async () => { - const { kDeviceHasNoExtraCapabilities } = await import('../../../main/services/driver') - - // Raw list. - await expect(globalThis.services.driver.list()).resolves.toContainEqual({ - enable: true, - guid: kDriverGuid, - localized: { - en: { - title: 'Tesla smart SDI-compatible switch', - company: 'Tesla Elec Technology Co.,Ltd', - provider: 'BridgeCmdr contributors' - } - }, - capabilities: kDeviceHasNoExtraCapabilities - }) - - // Localized list. - const { useDrivers } = await import('../../../renderer/services/driver') - const drivers = useDrivers() - await expect(drivers.all()).resolves.toBeUndefined() - expect(drivers.items).toContainEqual({ - guid: kDriverGuid, - title: 'Tesla smart SDI-compatible switch', - company: 'Tesla Elec Technology Co.,Ltd', - provider: 'BridgeCmdr contributors', - capabilities: kDeviceHasNoExtraCapabilities - }) -}) + const { default: driver } = await import('../../../main/drivers/tesla-smart/sdi') + const { default: useDrivers } = await import('../../../main/services/drivers') -test('connect', async () => { - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const drivers = useDrivers() - await expect(load(kDriverGuid, 'port:/dev/ttyS0')).resolves.not.toBeNull() + await expect(drivers.all()).resolves.toContainEqual(driver) + await expect(drivers.get(kDriverGuid)).resolves.toEqual(driver) }) test('power on', async (context) => { @@ -68,15 +35,14 @@ test('power on', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() // Power on is a no-op context.stream.withSequence() - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.powerOn()).resolves.toBeUndefined() + await expect(drivers.powerOn(kDriverGuid, 'port:/dev/ttyS0')).resolves.toBeUndefined() context.stream.sequence.expectDone() }) @@ -85,15 +51,14 @@ test('power off', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() // Power off is a no-op context.stream.withSequence() - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.powerOff()).resolves.toBeUndefined() + await expect(drivers.powerOff(kDriverGuid, 'port:/dev/ttyS0')).resolves.toBeUndefined() context.stream.sequence.expectDone() }) @@ -102,15 +67,14 @@ test('activate tie', async (context) => { context.stream = new stream.MockCommandStream() - const { useDrivers } = await import('../../../renderer/services/driver') - const { load } = useDrivers() + const { default: useDrivers } = await import('../../../main/services/drivers') + const drivers = useDrivers() const input = 1 const command = Buffer.of(0xaa, 0xcc, 0x01, input) context.stream.withSequence().on(Buffer.from(command), () => Buffer.from('Good')) - const driver = await load(kDriverGuid, 'port:/dev/ttyS0') - await expect(driver.activate(input, 0, 0)).resolves.toBeUndefined() + await expect(drivers.activate(kDriverGuid, 'port:/dev/ttyS0', input, 0, 0)).resolves.toBeUndefined() context.stream.sequence.expectDone() }) diff --git a/src/tests/level.test.ts b/src/tests/level.test.ts index c9bd4b5..7f9b44e 100644 --- a/src/tests/level.test.ts +++ b/src/tests/level.test.ts @@ -2,14 +2,13 @@ import { test, expect, vi, beforeEach, afterEach } from 'vitest' const mock = await vi.hoisted(async () => await import('./support/mock')) -beforeEach(async () => { +beforeEach(() => { vi.mock(import('electron'), mock.electronModule) vi.mock(import('electron-log')) - await mock.bridgeCmdrBasics() }) -afterEach(async () => { - await globalThis.services.freeAllHandles() +afterEach(() => { + vi.restoreAllMocks() vi.resetModules() }) diff --git a/src/tests/support/mock.ts b/src/tests/support/mock.ts index 0161142..51db37c 100644 --- a/src/tests/support/mock.ts +++ b/src/tests/support/mock.ts @@ -67,24 +67,6 @@ export async function electronModule(original: () => Promise