Add MicroslopSam TTS, Flextalk voice profiles, effects, and engine controls
This commit is contained in:
+26
-5
@@ -12,6 +12,9 @@ import { mainWin } from "./mainWindow";
|
||||
|
||||
const resolvers = new Map<string, Record<"resolve" | "reject", (data: any) => void>>();
|
||||
|
||||
/** Renderer commands that never answer must not leak their resolver or hang the caller. */
|
||||
const RENDERER_TIMEOUT_MS = 15_000;
|
||||
|
||||
export interface IpcMessage {
|
||||
nonce: string;
|
||||
message: string;
|
||||
@@ -31,18 +34,36 @@ export interface IpcResponse {
|
||||
* You must add a handler for the message in the renderer process.
|
||||
*/
|
||||
export function sendRendererCommand<T = any>(message: string, data?: any) {
|
||||
if (mainWin.isDestroyed()) {
|
||||
console.warn("Main window is destroyed, cannot send IPC command:", message);
|
||||
return Promise.reject(new Error("Main window is destroyed"));
|
||||
if (mainWin.isDestroyed() || mainWin.webContents.isDestroyed() || mainWin.webContents.isCrashed()) {
|
||||
return Promise.reject(new Error("the client renderer is not available"));
|
||||
}
|
||||
|
||||
const nonce = randomUUID();
|
||||
|
||||
const promise = new Promise<T>((resolve, reject) => {
|
||||
resolvers.set(nonce, { resolve, reject });
|
||||
const timer = setTimeout(() => {
|
||||
resolvers.delete(nonce);
|
||||
reject(new Error(`the renderer did not answer ${message} in time`));
|
||||
}, RENDERER_TIMEOUT_MS);
|
||||
|
||||
resolvers.set(nonce, {
|
||||
resolve: value => {
|
||||
clearTimeout(timer);
|
||||
resolve(value);
|
||||
},
|
||||
reject: error => {
|
||||
clearTimeout(timer);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
mainWin.webContents.send(IpcEvents.IPC_COMMAND, { nonce, message, data });
|
||||
try {
|
||||
mainWin.webContents.send(IpcEvents.IPC_COMMAND, { nonce, message, data });
|
||||
} catch (error) {
|
||||
resolvers.delete(nonce);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
@@ -255,7 +255,14 @@ function initSettingsListeners(win: BrowserWindow) {
|
||||
}
|
||||
|
||||
async function initSpellCheckLanguages(win: BrowserWindow, languages?: string[]) {
|
||||
languages ??= await sendRendererCommand(IpcCommands.GET_LANGUAGES);
|
||||
if (!languages) {
|
||||
try {
|
||||
languages = await sendRendererCommand(IpcCommands.GET_LANGUAGES);
|
||||
} catch {
|
||||
// The renderer may be loading or gone; spell check keeps its default.
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!languages) return;
|
||||
|
||||
const ses = session.defaultSession;
|
||||
|
||||
+19
-22
@@ -21,6 +21,7 @@ import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
||||
import {
|
||||
PUPPET_LIMITS,
|
||||
PuppetAction,
|
||||
PuppetActions,
|
||||
PuppetErrorCode,
|
||||
type PuppetEvent,
|
||||
type PuppetRequest,
|
||||
@@ -36,30 +37,13 @@ export const PUPPET_SOCKET_PATH =
|
||||
? "\\\\?\\pipe\\vesktop-puppet.sock"
|
||||
: join(env.XDG_RUNTIME_DIR || env.TMPDIR || env.TMP || env.TEMP || "/tmp", "vesktop-puppet.sock");
|
||||
|
||||
const KNOWN_ACTIONS = new Set<string>([
|
||||
PuppetAction.Ping,
|
||||
PuppetAction.EventsSubscribe,
|
||||
PuppetAction.StateGet,
|
||||
PuppetAction.VoiceGet,
|
||||
PuppetAction.VoiceOverview,
|
||||
PuppetAction.VoiceJoin,
|
||||
PuppetAction.VoiceLeave,
|
||||
PuppetAction.VoiceMove,
|
||||
PuppetAction.VoiceUsers,
|
||||
PuppetAction.UserStatus,
|
||||
PuppetAction.VoiceDevices,
|
||||
PuppetAction.VoiceSetDevices,
|
||||
PuppetAction.UserSearch,
|
||||
PuppetAction.DmList,
|
||||
PuppetAction.MessageSend,
|
||||
PuppetAction.MessageRead,
|
||||
PuppetAction.MessageFetch,
|
||||
PuppetAction.ReactionAdd,
|
||||
PuppetAction.DmSend
|
||||
]);
|
||||
// Derived from the shared list so a new action cannot be added in the protocol
|
||||
// yet rejected by the bridge.
|
||||
const KNOWN_ACTIONS = new Set<string>(PuppetActions);
|
||||
|
||||
let server: Server | undefined;
|
||||
const subscribers = new Set<Socket>();
|
||||
let rendererWarned = false;
|
||||
|
||||
export async function initPuppetBridge() {
|
||||
await rm(PUPPET_SOCKET_PATH, { force: true });
|
||||
@@ -148,11 +132,24 @@ async function handleLine(socket: Socket, line: string) {
|
||||
|
||||
try {
|
||||
const result = await sendRendererCommand<PuppetResult>(IpcCommands.PUPPET_COMMAND, { action, params });
|
||||
rendererWarned = false;
|
||||
|
||||
if (result?.ok === false) respond(socket, { id, ok: false, error: result.error });
|
||||
else respond(socket, { id, ok: true, result: result?.result });
|
||||
} catch (error) {
|
||||
console.error("[puppet] action failed:", action, error);
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
|
||||
// A dead renderer is expected during a restart; the daemon polls every
|
||||
// second, so log the outage once instead of once per command.
|
||||
if (message === "the client renderer is not available") {
|
||||
if (!rendererWarned) {
|
||||
rendererWarned = true;
|
||||
console.warn("[puppet] the client renderer is not available; commands fail until it reloads");
|
||||
}
|
||||
} else {
|
||||
console.error("[puppet] action failed:", action, error);
|
||||
}
|
||||
|
||||
respond(socket, {
|
||||
id,
|
||||
ok: false,
|
||||
|
||||
Reference in New Issue
Block a user