diff --git a/src/common/AmongUsState.ts b/src/common/AmongUsState.ts
index 6c71e0050..adb2c9c44 100644
--- a/src/common/AmongUsState.ts
+++ b/src/common/AmongUsState.ts
@@ -85,5 +85,6 @@ export interface VoiceState {
localIsAlive: boolean;
muted: boolean;
deafened: boolean;
+ effectivelyMuted: boolean;
mod: ModsType;
}
diff --git a/src/main/index.ts b/src/main/index.ts
index e1cec42f2..ea50763f5 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -18,9 +18,11 @@ import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-insta
import { gameReader } from './hook';
import { GenerateHat } from './avatarGenerator';
const args = require('minimist')(process.argv); // eslint-disable-line
+import * as http from 'http';
+import { readFileSync } from 'fs';
const isDevelopment = process.env.NODE_ENV !== 'production';
const devTools = (isDevelopment || args.dev === 1) && true;
-const appVersion: string = isDevelopment? "DEV" : autoUpdater.currentVersion.version;
+const appVersion: string = isDevelopment ? "DEV" : autoUpdater.currentVersion.version;
declare global {
namespace NodeJS {
@@ -35,6 +37,117 @@ declare global {
// global reference to mainWindow (necessary to prevent window from being garbage collected)
global.mainWindow = null;
global.overlay = null;
+
+let isMutedGlobal = false;
+let isDeafenedGlobal = false;
+
+export function updateMuteStatus(muted: boolean, deafened: boolean) {
+ isMutedGlobal = muted;
+ isDeafenedGlobal = deafened;
+}
+
+function startMicrophoneStatusServer() {
+ const server = http.createServer((req, res) => {
+ if (!req.url) return;
+
+ if (req.url.startsWith('/status')) {
+ res.writeHead(200, {
+ 'Content-Type': 'application/json',
+ 'Access-Control-Allow-Origin': '*',
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ 'Pragma': 'no-cache',
+ 'Expires': '0'
+ });
+ res.end(JSON.stringify({ muted: isMutedGlobal, deafened: isDeafenedGlobal }));
+ return;
+ }
+
+ if (req.url === '/Muted.png' || req.url === '/Unmuted.png' || req.url.startsWith('/icon')) {
+ const iconName = req.url.startsWith('/icon') ? (isMutedGlobal || isDeafenedGlobal ? 'Muted.png' : 'Unmuted.png') : req.url.slice(1).split('?')[0];
+
+ const possiblePaths = isDevelopment ? [
+ joinPath(__dirname, '..', '..', 'static', iconName),
+ joinPath(process.cwd(), 'static', iconName),
+ joinPath(__dirname, 'static', iconName)
+ ] : [
+ joinPath(__dirname, '..', 'renderer', 'static', iconName),
+ joinPath(app.getAppPath(), 'dist', 'renderer', 'static', iconName),
+ joinPath(process.resourcesPath, 'static', iconName),
+ joinPath(__dirname, iconName)
+ ];
+
+ let image = null;
+ for (const path of possiblePaths) {
+ try {
+ image = readFileSync(path);
+ if (image) break;
+ } catch (e) { }
+ }
+
+ if (image) {
+ res.writeHead(200, {
+ 'Content-Type': 'image/png',
+ 'Access-Control-Allow-Origin': '*',
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ 'Pragma': 'no-cache',
+ 'Expires': '0'
+ });
+ res.end(image);
+ } else {
+ res.writeHead(404);
+ res.end();
+ }
+ return;
+ }
+
+ res.writeHead(200, { 'Content-Type': 'text/html' });
+ res.end(`
+
+
+
+ Microphone Status Overlay
+
+
+
+
+
+
+
+ `);
+ });
+
+ server.listen(4697, '0.0.0.0', () => {
+ console.log('Microphone Status Overlay server running on http://localhost:4697');
+ });
+}
+
const store = new Store();
app.commandLine.appendSwitch('disable-pinch');
@@ -43,7 +156,7 @@ if (platform() === 'linux' || !store.get('hardware_acceleration', true)) {
}
-if(platform() === 'linux'){
+if (platform() === 'linux') {
app.commandLine.appendSwitch('disable-gpu-sandbox');
}
@@ -304,6 +417,8 @@ if (!gotTheLock) {
callback(pathname);
});
+ startMicrophoneStatusServer();
+
protocol.registerFileProtocol('generate', async (request, callback) => {
const url = new URL(request.url.replace('generate:///', ''));
const path = await GenerateHat(url, gameReader.playercolors, Number(url.searchParams.get('color')), '');
diff --git a/src/main/ipc-handlers.ts b/src/main/ipc-handlers.ts
index f112a9bdf..995d33f04 100644
--- a/src/main/ipc-handlers.ts
+++ b/src/main/ipc-handlers.ts
@@ -14,6 +14,7 @@ import path from 'path';
import fs from 'fs';
import { IpcMessages, IpcOverlayMessages } from '../common/ipc-messages';
+import { updateMuteStatus } from './index';
// Listeners are fire and forget, they do not have "responses" or return values
export const initializeIpcListeners = (): void => {
@@ -50,8 +51,15 @@ export const initializeIpcListeners = (): void => {
app.quit();
});
- ipcMain.on(IpcMessages.SEND_TO_OVERLAY, (_, event: IpcOverlayMessages, ...args: unknown[]) => {
+ ipcMain.on(IpcMessages.SEND_TO_OVERLAY, (_, event: IpcOverlayMessages, ...args: any[]) => {
try {
+ if (event === IpcOverlayMessages.NOTIFY_VOICE_STATE_CHANGED) {
+ const state = args[0];
+ if (state) {
+ const isMuted = state.effectivelyMuted !== undefined ? state.effectivelyMuted : (state.muted || false);
+ updateMuteStatus(isMuted, state.deafened || false);
+ }
+ }
if (global.overlay) global.overlay.webContents.send(event, ...args);
} catch (e) {
/*empty*/
diff --git a/src/renderer/Voice.tsx b/src/renderer/Voice.tsx
index e6a3adbbc..36c90971e 100644
--- a/src/renderer/Voice.tsx
+++ b/src/renderer/Voice.tsx
@@ -944,6 +944,7 @@ const Voice: React.FC = function ({ t, error: initialError }: VoiceP
if (!connectionStuff.current.deafened && !connectionStuff.current.muted) {
inStream.getAudioTracks()[0].enabled =
connectionStuff.current.pushToTalkMode === pushToTalkOptions.PUSH_TO_TALK ? pressing : !pressing;
+ setTalking(pressing);
}
});
@@ -1305,9 +1306,7 @@ const Voice: React.FC = function ({ t, error: initialError }: VoiceP
// Pass voice state to overlay
useEffect(() => {
- if (!settings.enableOverlay) {
- return;
- }
+ const isEffectivelyMuted = mutedState || deafenedState || (settings.pushToTalkMode !== pushToTalkOptions.VOICE && !talking);
ipcRenderer.send(IpcMessages.SEND_TO_OVERLAY, IpcOverlayMessages.NOTIFY_VOICE_STATE_CHANGED, {
otherTalking,
playerSocketIds: playerSocketIdsRef.current,
@@ -1319,6 +1318,7 @@ const Voice: React.FC = function ({ t, error: initialError }: VoiceP
impostorRadioClientId: !myPlayer?.isImpostor ? -1 : impostorRadioClientId.current,
muted: mutedState,
deafened: deafenedState,
+ effectivelyMuted: isEffectivelyMuted,
mod: gameState.mod,
} as VoiceState);
}, [
@@ -1330,6 +1330,8 @@ const Voice: React.FC = function ({ t, error: initialError }: VoiceP
mutedState,
deafenedState,
impostorRadioClientId.current,
+ gameState.mod,
+ myPlayer?.isDead
]);
return (
diff --git a/static/Muted.png b/static/Muted.png
new file mode 100644
index 000000000..c9d226a2b
Binary files /dev/null and b/static/Muted.png differ
diff --git a/static/Unmuted.png b/static/Unmuted.png
new file mode 100644
index 000000000..d2cf4b49f
Binary files /dev/null and b/static/Unmuted.png differ