cleaning dependency in windowmanager

This commit is contained in:
sanio 2025-07-15 18:00:31 +09:00
parent 698473007a
commit f755fdb9e3
8 changed files with 215 additions and 257 deletions

View File

@ -91,6 +91,7 @@ module.exports = {
ipcMain.handle('listen:startMacosSystemAudio', async () => await listenService.handleStartMacosAudio());
ipcMain.handle('listen:stopMacosSystemAudio', async () => await listenService.handleStopMacosAudio());
ipcMain.handle('update-google-search-setting', async (event, enabled) => await listenService.handleUpdateGoogleSearchSetting(enabled));
ipcMain.handle('listen:isSessionActive', async () => await listenService.isSessionActive());
ipcMain.handle('listen:changeSession', async (event, listenButtonText) => {
console.log('[FeatureBridge] listen:changeSession from mainheader', listenButtonText);
try {

View File

@ -20,7 +20,6 @@ module.exports = {
ipcMain.on('header-state-changed', (event, state) => windowManager.handleHeaderStateChanged(state));
ipcMain.on('header-animation-finished', (event, state) => windowManager.handleHeaderAnimationFinished(state));
ipcMain.handle('get-header-position', () => windowManager.getHeaderPosition());
ipcMain.handle('move-header', (event, newX, newY) => windowManager.moveHeader(newX, newY));
ipcMain.handle('move-header-to', (event, newX, newY) => windowManager.moveHeaderTo(newX, newY));
ipcMain.handle('adjust-window-height', (event, targetHeight) => windowManager.adjustWindowHeight(event.sender, targetHeight));
},

View File

@ -54,53 +54,7 @@ class ListenService {
}
async handleListenRequest(listenButtonText) {
const { windowPool, updateLayout } = require('../../window/windowManager');
const listenWindow = windowPool.get('listen');
const header = windowPool.get('header');
try {
switch (listenButtonText) {
case 'Listen':
console.log('[ListenService] changeSession to "Listen"');
listenWindow.show();
updateLayout();
listenWindow.webContents.send('window-show-animation');
await this.initializeSession();
listenWindow.webContents.send('session-state-changed', { isActive: true });
break;
case 'Stop':
console.log('[ListenService] changeSession to "Stop"');
await this.closeSession();
listenWindow.webContents.send('session-state-changed', { isActive: false });
break;
case 'Done':
console.log('[ListenService] changeSession to "Done"');
listenWindow.webContents.send('window-hide-animation');
listenWindow.webContents.send('session-state-changed', { isActive: false });
break;
default:
throw new Error(`[ListenService] unknown listenButtonText: ${listenButtonText}`);
}
header.webContents.send('listen:changeSessionResult', { success: true });
} catch (error) {
console.error('[ListenService] error in handleListenRequest:', error);
header.webContents.send('listen:changeSessionResult', { success: false });
throw error;
}
}
initialize() {
this.setupIpcHandlers();
console.log('[ListenService] Initialized and ready.');
}
async handleListenRequest(listenButtonText) {
const { windowPool, updateLayout } = require('../../window/windowManager');
const { windowPool } = require('../../window/windowManager');
const listenWindow = windowPool.get('listen');
const header = windowPool.get('header');

View File

@ -273,7 +273,7 @@ contextBridge.exposeInMainWorld('api', {
stopMacosSystemAudio: () => ipcRenderer.invoke('listen:stopMacosSystemAudio'),
// Session Management
isSessionActive: () => ipcRenderer.invoke('is-session-active'),
isSessionActive: () => ipcRenderer.invoke('listen:isSessionActive'),
// Listeners
onSystemAudioData: (callback) => ipcRenderer.on('system-audio-data', callback),

View File

@ -422,6 +422,12 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
try {
if (isMacOS) {
const sessionActive = await window.api.listenCapture.isSessionActive();
if (!sessionActive) {
throw new Error('STT sessions not initialized - please wait for initialization to complete');
}
// On macOS, use SystemAudioDump for audio and getDisplayMedia for screen
console.log('Starting macOS capture with SystemAudioDump...');
@ -466,6 +472,12 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
console.log('macOS screen capture started - audio handled by SystemAudioDump');
} else if (isLinux) {
const sessionActive = await window.api.listenCapture.isSessionActive();
if (!sessionActive) {
throw new Error('STT sessions not initialized - please wait for initialization to complete');
}
// Linux - use display media for screen capture and getUserMedia for microphone
mediaStream = await navigator.mediaDevices.getDisplayMedia({
video: {

View File

@ -1,11 +1,27 @@
const { screen } = require('electron');
function getCurrentDisplay(window) {
if (!window || window.isDestroyed()) return screen.getPrimaryDisplay();
const windowBounds = window.getBounds();
const windowCenter = {
x: windowBounds.x + windowBounds.width / 2,
y: windowBounds.y + windowBounds.height / 2,
};
return screen.getDisplayNearestPoint(windowCenter);
}
function getDisplayById(displayId) {
const displays = screen.getAllDisplays();
return displays.find(d => d.id === displayId) || screen.getPrimaryDisplay();
}
class SmoothMovementManager {
constructor(windowPool, getDisplayById, getCurrentDisplay, updateLayout) {
constructor(windowPool, layoutManager) {
this.windowPool = windowPool;
this.getDisplayById = getDisplayById;
this.getCurrentDisplay = getCurrentDisplay;
this.updateLayout = updateLayout;
this.layoutManager = layoutManager;
this.stepSize = 80;
this.animationDuration = 300;
this.headerPosition = { x: 0, y: 0 };
@ -39,11 +55,11 @@ class SmoothMovementManager {
const header = this.windowPool.get('header');
if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return;
const targetDisplay = this.getDisplayById(displayId);
const targetDisplay = getDisplayById(displayId);
if (!targetDisplay) return;
const currentBounds = header.getBounds();
const currentDisplay = this.getCurrentDisplay(header);
const currentDisplay = getCurrentDisplay(header);
if (currentDisplay.id === targetDisplay.id) return;
@ -60,56 +76,6 @@ class SmoothMovementManager {
this.currentDisplayId = targetDisplay.id;
}
hideToEdge(edge, callback, { instant = false } = {}) {
const header = this.windowPool.get('header');
if (!header || header.isDestroyed()) {
if (typeof callback === 'function') callback();
return;
}
const { x, y } = header.getBounds();
this.lastVisiblePosition = { x, y };
this.hiddenPosition = { edge };
if (instant) {
header.hide();
if (typeof callback === 'function') callback();
return;
}
header.webContents.send('window-hide-animation');
setTimeout(() => {
if (!header.isDestroyed()) header.hide();
if (typeof callback === 'function') callback();
}, 5);
}
showFromEdge(callback) {
const header = this.windowPool.get('header');
if (!header || header.isDestroyed()) {
if (typeof callback === 'function') callback();
return;
}
// 숨기기 전에 기억해둔 위치 복구
if (this.lastVisiblePosition) {
header.setPosition(
this.lastVisiblePosition.x,
this.lastVisiblePosition.y,
false // animate: false
);
}
header.show();
header.webContents.send('window-show-animation');
// 내부 상태 초기화
this.hiddenPosition = null;
this.lastVisiblePosition = null;
if (typeof callback === 'function') callback();
}
moveStep(direction) {
const header = this.windowPool.get('header');
@ -211,7 +177,7 @@ class SmoothMovementManager {
setTimeout(step, 8); // requestAnimationFrame 대신 setTimeout으로 간결하게 처리
} else {
// 애니메이션 종료
this.updateLayout(); // 레이아웃 재정렬
this.layoutManager.updateLayout(); // 레이아웃 재정렬
if (onComplete) {
onComplete(); // 완료 콜백 실행
}
@ -267,7 +233,7 @@ class SmoothMovementManager {
// Update header position to the actual final position
this.headerPosition = { x: Math.round(targetX), y: Math.round(targetY) };
}
this.updateLayout();
this.layoutManager.updateLayout();
}
};
animate();
@ -277,7 +243,7 @@ class SmoothMovementManager {
const header = this.windowPool.get('header');
if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return;
const display = this.getCurrentDisplay(header);
const display = getCurrentDisplay(header);
const { width, height } = display.workAreaSize;
const { x: workAreaX, y: workAreaY } = display.workArea;
const currentBounds = header.getBounds();
@ -313,7 +279,7 @@ class SmoothMovementManager {
});
this.headerPosition = { x: targetX, y: targetY };
this.updateLayout();
this.layoutManager.updateLayout();
}
destroy() {

View File

@ -37,6 +37,110 @@ class WindowLayoutManager {
});
}
getHeaderPosition = () => {
const header = this.windowPool.get('header');
if (header) {
const [x, y] = header.getPosition();
return { x, y };
}
return { x: 0, y: 0 };
};
resizeHeaderWindow = ({ width, height }) => {
const header = this.windowPool.get('header');
if (header) {
console.log(`[WindowManager] Resize request: ${width}x${height}`);
const currentBounds = header.getBounds();
console.log(`[WindowManager] Current bounds: ${currentBounds.width}x${currentBounds.height} at (${currentBounds.x}, ${currentBounds.y})`);
if (currentBounds.width === width && currentBounds.height === height) {
console.log('[WindowManager] Already at target size, skipping resize');
return { success: true };
}
const wasResizable = header.isResizable();
if (!wasResizable) {
header.setResizable(true);
}
const centerX = currentBounds.x + currentBounds.width / 2;
const newX = Math.round(centerX - width / 2);
const display = getCurrentDisplay(header);
const { x: workAreaX, width: workAreaWidth } = display.workArea;
const clampedX = Math.max(workAreaX, Math.min(workAreaX + workAreaWidth - width, newX));
header.setBounds({ x: clampedX, y: currentBounds.y, width, height });
if (!wasResizable) {
header.setResizable(false);
}
this.updateLayout();
return { success: true };
}
return { success: false, error: 'Header window not found' };
};
moveHeaderTo = (newX, newY) => {
const header = this.windowPool.get('header');
if (header) {
const targetDisplay = screen.getDisplayNearestPoint({ x: newX, y: newY });
const { x: workAreaX, y: workAreaY, width, height } = targetDisplay.workArea;
const headerBounds = header.getBounds();
let clampedX = newX;
let clampedY = newY;
if (newX < workAreaX) {
clampedX = workAreaX;
} else if (newX + headerBounds.width > workAreaX + width) {
clampedX = workAreaX + width - headerBounds.width;
}
if (newY < workAreaY) {
clampedY = workAreaY;
} else if (newY + headerBounds.height > workAreaY + height) {
clampedY = workAreaY + height - headerBounds.height;
}
header.setPosition(clampedX, clampedY, false);
this.updateLayout();
}
};
adjustWindowHeight = (sender, targetHeight) => {
const senderWindow = this.windowPool.get(sender);
if (senderWindow) {
const wasResizable = senderWindow.isResizable();
if (!wasResizable) {
senderWindow.setResizable(true);
}
const currentBounds = senderWindow.getBounds();
const minHeight = senderWindow.getMinimumSize()[1];
const maxHeight = senderWindow.getMaximumSize()[1];
let adjustedHeight;
if (maxHeight === 0) {
adjustedHeight = Math.max(minHeight, targetHeight);
} else {
adjustedHeight = Math.max(minHeight, Math.min(maxHeight, targetHeight));
}
senderWindow.setSize(currentBounds.width, adjustedHeight, false);
if (!wasResizable) {
senderWindow.setResizable(false);
}
this.updateLayout();
}
};
/**
*
* @param {object} [visibilityOverride] - { listen: true, ask: true }

View File

@ -38,11 +38,6 @@ let settingsHideTimer = null;
let layoutManager = null;
function updateLayout() {
if (layoutManager) {
layoutManager.updateLayout();
}
}
let movementManager = null;
/**
@ -92,6 +87,30 @@ const cancelHideSettingsWindow = () => {
internalBridge.emit('window:requestVisibility', { name: 'settings', visible: true });
};
const moveWindowStep = (direction) => {
internalBridge.emit('window:moveStep', { direction });
};
const resizeHeaderWindow = ({ width, height }) => {
internalBridge.emit('window:resizeHeaderWindow', { width, height });
};
const handleHeaderAnimationFinished = (state) => {
internalBridge.emit('window:headerAnimationFinished', state);
};
const getHeaderPosition = () => {
internalBridge.emit('window:getHeaderPosition');
};
const moveHeaderTo = (newX, newY) => {
internalBridge.emit('window:moveHeaderTo', { newX, newY });
};
const adjustWindowHeight = (sender, targetHeight) => {
internalBridge.emit('window:adjustWindowHeight', { sender, targetHeight });
};
function setupWindowController(windowPool, layoutManager, movementManager) {
internalBridge.on('window:requestVisibility', ({ name, visible }) => {
@ -109,6 +128,21 @@ function setupWindowController(windowPool, layoutManager, movementManager) {
internalBridge.on('window:moveStep', ({ direction }) => {
movementManager.moveStep(direction);
});
internalBridge.on('window:resizeHeaderWindow', ({ width, height }) => {
resizingHeaderWindow(layoutManager, movementManager, { width, height });
});
internalBridge.on('window:headerAnimationFinished', (state) => {
handlingHeaderAnimationFinished(windowPool, layoutManager, state);
});
internalBridge.on('window:getHeaderPosition', () => {
gettingHeaderPosition(layoutManager);
});
internalBridge.on('window:moveHeaderTo', ({ newX, newY }) => {
movingHeaderTo(layoutManager, newX, newY);
});
internalBridge.on('window:adjustWindowHeight', ({ sender, targetHeight }) => {
adjustingWindowHeight(layoutManager, sender, targetHeight);
});
}
function changeAllWindowsVisibility(windowPool, targetVisibility) {
@ -249,8 +283,8 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement
const otherWin = windowPool.get(otherName);
const isOtherWinVisible = otherWin && !otherWin.isDestroyed() && otherWin.isVisible();
const ANIM_OFFSET_X = 100;
const ANIM_OFFSET_Y = 20;
const ANIM_OFFSET_X = 50;
const ANIM_OFFSET_Y = 20;
if (shouldBeVisible) {
win.setOpacity(0);
@ -305,7 +339,7 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement
} else {
const currentBounds = win.getBounds();
fadeWindow(
win, 1, 0, undefined,
win, 1, 0, 250,
() => win.hide()
);
if (name === 'listen') {
@ -313,7 +347,7 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement
const targetX = currentBounds.x - ANIM_OFFSET_X;
movementManager.animateWindow(win, targetX, currentBounds.y);
} else {
const targetX = currentBounds.x - currentBounds.width;
const targetX = currentBounds.x - ANIM_OFFSET_X;
movementManager.animateWindow(win, targetX, currentBounds.y);
}
} else if (name === 'ask') {
@ -353,52 +387,6 @@ const toggleContentProtection = () => {
return newStatus;
};
const resizeHeaderWindow = ({ width, height }) => {
const header = windowPool.get('header');
if (header) {
console.log(`[WindowManager] Resize request: ${width}x${height}`);
if (movementManager && movementManager.isAnimating) {
console.log('[WindowManager] Skipping resize during animation');
return { success: false, error: 'Cannot resize during animation' };
}
const currentBounds = header.getBounds();
console.log(`[WindowManager] Current bounds: ${currentBounds.width}x${currentBounds.height} at (${currentBounds.x}, ${currentBounds.y})`);
if (currentBounds.width === width && currentBounds.height === height) {
console.log('[WindowManager] Already at target size, skipping resize');
return { success: true };
}
const wasResizable = header.isResizable();
if (!wasResizable) {
header.setResizable(true);
}
const centerX = currentBounds.x + currentBounds.width / 2;
const newX = Math.round(centerX - width / 2);
const display = getCurrentDisplay(header);
const { x: workAreaX, width: workAreaWidth } = display.workArea;
const clampedX = Math.max(workAreaX, Math.min(workAreaX + workAreaWidth - width, newX));
header.setBounds({ x: clampedX, y: currentBounds.y, width, height });
if (!wasResizable) {
header.setResizable(false);
}
if (updateLayout) {
updateLayout();
}
return { success: true };
}
return { success: false, error: 'Header window not found' };
};
const openLoginPage = () => {
const webUrl = process.env.pickleglass_WEB_URL || 'http://localhost:3000';
@ -407,12 +395,6 @@ const openLoginPage = () => {
console.log('Opening personalization page:', personalizeUrl);
};
const moveWindowStep = (direction) => {
if (movementManager) {
movementManager.moveStep(direction);
}
};
function createFeatureWindows(header, namesToCreate) {
// if (windowPool.has('listen')) return;
@ -617,14 +599,6 @@ function getCurrentDisplay(window) {
return screen.getDisplayNearestPoint(windowCenter);
}
function getDisplayById(displayId) {
const displays = screen.getAllDisplays();
return displays.find(d => d.id === displayId) || screen.getPrimaryDisplay();
}
function createWindows() {
@ -636,8 +610,7 @@ function createWindows() {
const initialX = Math.round((screenWidth - DEFAULT_WINDOW_WIDTH) / 2);
const initialY = workAreaY + 21;
movementManager = new SmoothMovementManager(windowPool, getDisplayById, getCurrentDisplay, updateLayout);
const header = new BrowserWindow({
width: DEFAULT_WINDOW_WIDTH,
height: HEADER_HEIGHT,
@ -687,15 +660,17 @@ function createWindows() {
});
}
windowPool.set('header', header);
header.on('moved', updateLayout);
layoutManager = new WindowLayoutManager(windowPool);
movementManager = new SmoothMovementManager(windowPool, layoutManager);
header.on('moved', () => layoutManager.updateLayout());
header.webContents.once('dom-ready', () => {
shortcutsService.initialize(windowPool);
shortcutsService.registerShortcuts();
});
setupIpcHandlers(movementManager);
setupIpcHandlers(movementManager, layoutManager);
setupWindowController(windowPool, layoutManager, movementManager);
if (currentHeaderState === 'main') {
@ -729,13 +704,13 @@ function createWindows() {
header.on('resize', () => {
console.log('[WindowManager] Header resize event triggered');
updateLayout();
layoutManager.updateLayout();
});
return windowPool;
}
function setupIpcHandlers(movementManager) {
function setupIpcHandlers(movementManager, layoutManager) {
// quit-application handler moved to windowBridge.js to avoid duplication
screen.on('display-added', (event, newDisplay) => {
console.log('[Display] New display added:', newDisplay.id);
@ -752,10 +727,11 @@ function setupIpcHandlers(movementManager) {
screen.on('display-metrics-changed', (event, display, changedMetrics) => {
// console.log('[Display] Display metrics changed:', display.id, changedMetrics);
updateLayout();
layoutManager.updateLayout();
});
}
const handleHeaderStateChanged = (state) => {
console.log(`[WindowManager] Header state changed to: ${state}`);
currentHeaderState = state;
@ -768,7 +744,17 @@ const handleHeaderStateChanged = (state) => {
internalBridge.emit('reregister-shortcuts');
};
const handleHeaderAnimationFinished = (state) => {
const resizingHeaderWindow = (layoutManager, movementManager, { width, height }) => {
if (movementManager.isAnimating) {
console.log('[WindowManager] Skipping resize during animation');
return { success: false, error: 'Cannot resize during animation' };
}
return layoutManager.resizeHeaderWindow({ width, height });
};
const handlingHeaderAnimationFinished = (windowPool, layoutManager, state) => {
const header = windowPool.get('header');
if (!header || header.isDestroyed()) return;
@ -777,87 +763,24 @@ const handleHeaderAnimationFinished = (state) => {
console.log('[WindowManager] Header hidden after animation.');
} else if (state === 'visible') {
console.log('[WindowManager] Header shown after animation.');
updateLayout();
layoutManager.updateLayout();
}
};
const getHeaderPosition = () => {
const header = windowPool.get('header');
if (header) {
const [x, y] = header.getPosition();
return { x, y };
}
return { x: 0, y: 0 };
const gettingHeaderPosition = (layoutManager) => {
return layoutManager.getHeaderPosition();
};
const moveHeader = (newX, newY) => {
const header = windowPool.get('header');
if (header) {
const currentY = newY !== undefined ? newY : header.getBounds().y;
header.setPosition(newX, currentY, false);
updateLayout();
}
const movingHeaderTo = (layoutManager, newX, newY) => {
layoutManager.moveHeaderTo(newX, newY);
};
const moveHeaderTo = (newX, newY) => {
const header = windowPool.get('header');
if (header) {
const targetDisplay = screen.getDisplayNearestPoint({ x: newX, y: newY });
const { x: workAreaX, y: workAreaY, width, height } = targetDisplay.workArea;
const headerBounds = header.getBounds();
let clampedX = newX;
let clampedY = newY;
if (newX < workAreaX) {
clampedX = workAreaX;
} else if (newX + headerBounds.width > workAreaX + width) {
clampedX = workAreaX + width - headerBounds.width;
}
if (newY < workAreaY) {
clampedY = workAreaY;
} else if (newY + headerBounds.height > workAreaY + height) {
clampedY = workAreaY + height - headerBounds.height;
}
header.setPosition(clampedX, clampedY, false);
updateLayout();
}
};
const adjustWindowHeight = (sender, targetHeight) => {
const senderWindow = BrowserWindow.fromWebContents(sender);
if (senderWindow) {
const wasResizable = senderWindow.isResizable();
if (!wasResizable) {
senderWindow.setResizable(true);
}
const currentBounds = senderWindow.getBounds();
const minHeight = senderWindow.getMinimumSize()[1];
const maxHeight = senderWindow.getMaximumSize()[1];
let adjustedHeight;
if (maxHeight === 0) {
adjustedHeight = Math.max(minHeight, targetHeight);
} else {
adjustedHeight = Math.max(minHeight, Math.min(maxHeight, targetHeight));
}
senderWindow.setSize(currentBounds.width, adjustedHeight, false);
if (!wasResizable) {
senderWindow.setResizable(false);
}
updateLayout();
}
const adjustingWindowHeight = (layoutManager, sender, targetHeight) => {
layoutManager.adjustWindowHeight(sender, targetHeight);
};
module.exports = {
updateLayout,
createWindows,
windowPool,
toggleContentProtection,
@ -871,7 +794,6 @@ module.exports = {
handleHeaderStateChanged,
handleHeaderAnimationFinished,
getHeaderPosition,
moveHeader,
moveHeaderTo,
adjustWindowHeight,
};