From 9bfa6e30402bd0f44fe0ddc9662d1d997de02933 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Wed, 25 Mar 2026 06:14:14 +1100 Subject: [PATCH] feat: add window flash frame and audio notification sound handling --- cinny | 2 +- electron/main.js | 56 +++++++++++++++++++++++++++++++++++++++++++++ electron/preload.js | 10 +++++--- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/cinny b/cinny index a8eb404..9a00375 160000 --- a/cinny +++ b/cinny @@ -1 +1 @@ -Subproject commit a8eb404ff3c1cab755bedba129209caa2b3f3137 +Subproject commit 9a00375568aeec2dfbe35b62e2d5df09100c3992 diff --git a/electron/main.js b/electron/main.js index 71c963a..2066bf4 100644 --- a/electron/main.js +++ b/electron/main.js @@ -629,6 +629,17 @@ ipcMain.handle('window:focus', () => { } }); +ipcMain.handle('window:flash-frame', (event, flash = true) => { + console.log('[Main] window:flash-frame called with:', flash); + if (mainWindow) { + console.log('[Main] Calling flashFrame on mainWindow'); + mainWindow.flashFrame(flash); + return { success: true }; + } + console.log('[Main] No mainWindow available'); + return { success: false, error: 'No window' }; +}); + ipcMain.handle('window:is-maximized', () => { return mainWindow ? mainWindow.isMaximized() : false; }); @@ -682,6 +693,51 @@ ipcMain.handle('write-clipboard-text', async (event, text) => { } }); +// Play notification sound +ipcMain.handle('play-notification-sound', async (event, soundType = 'message') => { + try { + const resourcesPath = app.isPackaged + ? path.join(process.resourcesPath, 'app.asar.unpacked', 'cinny', 'public', 'sound') + : path.join(__dirname, '..', 'cinny', 'public', 'sound'); + + const soundFile = soundType === 'invite' ? 'invite.ogg' : 'notification.ogg'; + const soundPath = path.join(resourcesPath, soundFile); + + // Check if file exists + if (!fs.existsSync(soundPath)) { + console.error('[Audio] Sound file not found:', soundPath); + return { success: false, error: 'Sound file not found' }; + } + + // Play sound based on platform + let command; + if (process.platform === 'win32') { + // Use PowerShell's SoundPlayer for Windows + // Note: PowerShell can't play OGG directly, so we use Add-Type for Windows Media Player + const psCommand = `Add-Type -AssemblyName presentationCore; $mediaPlayer = New-Object System.Windows.Media.MediaPlayer; $mediaPlayer.Open([uri]'${soundPath.replace(/\\/g, '\\\\')}'); $mediaPlayer.Play(); Start-Sleep -Milliseconds 100`; + command = `powershell -Command "${psCommand}"`; + } else if (process.platform === 'darwin') { + // Use afplay on macOS + command = `afplay "${soundPath}"`; + } else { + // Use paplay on Linux (PulseAudio) with fallback to aplay + command = `paplay "${soundPath}" || aplay "${soundPath}"`; + } + + // Execute the command without blocking + exec(command, (error) => { + if (error) { + console.error('[Audio] Failed to play sound:', error); + } + }); + + return { success: true }; + } catch (error) { + console.error('[Audio] Error playing sound:', error); + return { success: false, error: error.message }; + } +}); + // Get YouTube stream using yt-dlp ipcMain.handle('get-youtube-stream', async (event, url) => { try { diff --git a/electron/preload.js b/electron/preload.js index b1856f2..c48b000 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -94,7 +94,8 @@ contextBridge.exposeInMainWorld('electron', { close: () => ipcRenderer.invoke('window:close'), isMaximized: () => ipcRenderer.invoke('window:is-maximized'), startDrag: () => ipcRenderer.invoke('window:start-drag'), - focus: () => ipcRenderer.invoke('window:focus') + focus: () => ipcRenderer.invoke('window:focus'), + flashFrame: (flash = true) => ipcRenderer.invoke('window:flash-frame', flash) }, // Clipboard @@ -102,8 +103,11 @@ contextBridge.exposeInMainWorld('electron', { readImage: () => ipcRenderer.invoke('read-clipboard-image'), writeText: (text) => ipcRenderer.invoke('write-clipboard-text', text) }, - - // External URLs + // Audio + audio: { + playNotificationSound: (soundType = 'message') => ipcRenderer.invoke('play-notification-sound', soundType) + }, + // External URLs shell: { openExternal: (url) => ipcRenderer.invoke('open-external-url', url) },