shortcut moved
This commit is contained in:
		
							parent
							
								
									6d708d6dcd
								
							
						
					
					
						commit
						73a6e1345e
					
				
							
								
								
									
										2
									
								
								aec
									
									
									
									
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								aec
									
									
									
									
									
								
							@ -1 +1 @@
 | 
			
		||||
Subproject commit 9e11f4f95707714464194bdfc9db0222ec5c6163
 | 
			
		||||
Subproject commit f00bb1fb948053c752b916adfee19f90644a0b2f
 | 
			
		||||
							
								
								
									
										19
									
								
								docs/refactor-plan.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								docs/refactor-plan.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
# Refactor Plan: Non-Window Logic Migration from windowManager.js
 | 
			
		||||
 | 
			
		||||
## Goal
 | 
			
		||||
`windowManager.js`를 순수 창 관리 모듈로 만들기 위해 비즈니스 로직을 해당 서비스와 `featureBridge.js`로 이전.
 | 
			
		||||
 | 
			
		||||
## Steps (based on initial plan)
 | 
			
		||||
1. **Shortcuts**: Completed. Logic moved to `shortcutsService.js` and IPC to `featureBridge.js`. Used `internalBridge` for coordination.
 | 
			
		||||
 | 
			
		||||
2. **Screenshot**: Next. Move `captureScreenshot` function and related IPC handlers from `windowManager.js` to `askService.js` (since it's primarily used there). Update `askService.js` to use its own screenshot method. Add IPC handlers to `featureBridge.js` if needed.
 | 
			
		||||
 | 
			
		||||
3. **System Permissions**: Create new `permissionService.js` in `src/features/common/services/`. Move all permission-related logic (check, request, open preferences, mark completed, etc.) and IPC handlers from `windowManager.js` to the new service and `featureBridge.js`.
 | 
			
		||||
 | 
			
		||||
4. **API Key / Model State**: Completely remove from `windowManager.js` (e.g., `setupApiKeyIPC` and helpers). Ensure all usages (e.g., in `askService.js`) directly require and use `modelStateService.js` instead.
 | 
			
		||||
 | 
			
		||||
## Notes
 | 
			
		||||
- Maintain original logic without changes.
 | 
			
		||||
- Break circular dependencies if found.
 | 
			
		||||
- Use `internalBridge` for inter-module communication where appropriate.
 | 
			
		||||
- After each step, verify no errors and test functionality. 
 | 
			
		||||
@ -5,6 +5,7 @@ const authService = require('../features/common/services/authService');
 | 
			
		||||
const whisperService = require('../features/common/services/whisperService');
 | 
			
		||||
const ollamaService = require('../features/common/services/ollamaService');
 | 
			
		||||
const modelStateService = require('../features/common/services/modelStateService');
 | 
			
		||||
const shortcutsService = require('../features/shortcuts/shortcutsService');
 | 
			
		||||
 | 
			
		||||
const askService = require('../features/ask/askService');
 | 
			
		||||
const listenService = require('../features/listen/listenService');
 | 
			
		||||
@ -26,6 +27,12 @@ module.exports = {
 | 
			
		||||
    ipcMain.handle('settings:ensure-ollama-ready', async () => await settingsService.ensureOllamaReady());
 | 
			
		||||
    ipcMain.handle('settings:shutdown-ollama', async () => await settingsService.shutdownOllama());
 | 
			
		||||
 | 
			
		||||
    // Shortcuts
 | 
			
		||||
    ipcMain.handle('get-current-shortcuts', async () => await shortcutsService.loadKeybinds());
 | 
			
		||||
    ipcMain.handle('get-default-shortcuts', async () => await shortcutsService.handleRestoreDefaults());
 | 
			
		||||
    ipcMain.handle('save-shortcuts', async (event, newKeybinds) => await shortcutsService.handleSaveShortcuts(newKeybinds));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // User/Auth
 | 
			
		||||
    ipcMain.handle('get-current-user', () => authService.getCurrentUser());
 | 
			
		||||
    ipcMain.handle('start-firebase-auth', async () => await authService.startFirebaseAuthFlow());
 | 
			
		||||
 | 
			
		||||
@ -2,9 +2,10 @@
 | 
			
		||||
const { EventEmitter } = require('events');
 | 
			
		||||
 | 
			
		||||
// FeatureCore와 WindowCore를 잇는 내부 이벤트 버스
 | 
			
		||||
module.exports = new EventEmitter();
 | 
			
		||||
const internalBridge = new EventEmitter();
 | 
			
		||||
module.exports = internalBridge;
 | 
			
		||||
 | 
			
		||||
// 예시 이벤트
 | 
			
		||||
internalBridge.on('content-protection-changed', (enabled) => {
 | 
			
		||||
  // windowManager에서 처리
 | 
			
		||||
});
 | 
			
		||||
// internalBridge.on('content-protection-changed', (enabled) => {
 | 
			
		||||
//   // windowManager에서 처리
 | 
			
		||||
// });
 | 
			
		||||
@ -1,159 +1,23 @@
 | 
			
		||||
// src/bridge/windowBridge.js
 | 
			
		||||
const { ipcMain, BrowserWindow, globalShortcut } = require('electron');
 | 
			
		||||
const { ipcMain } = require('electron');
 | 
			
		||||
const windowManager = require('../window/windowManager');
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  // windowManager에서 필요한 변수들을 매개변수로 받도록 수정
 | 
			
		||||
  initialize(windowPool, app, shell, getCurrentDisplay, createFeatureWindows, movementManager, getContentProtectionStatus, setContentProtection, updateLayout) {
 | 
			
		||||
    let settingsHideTimer = null;
 | 
			
		||||
  initialize() {
 | 
			
		||||
    ipcMain.handle('toggle-content-protection', () => windowManager.toggleContentProtection());
 | 
			
		||||
    ipcMain.handle('resize-header-window', (event, args) => windowManager.resizeHeaderWindow(args));
 | 
			
		||||
    ipcMain.handle('get-content-protection-status', () => windowManager.getContentProtectionStatus());
 | 
			
		||||
    ipcMain.handle('open-shortcut-editor', () => windowManager.openShortcutEditor());
 | 
			
		||||
    ipcMain.on('show-settings-window', (event, bounds) => windowManager.showSettingsWindow(bounds));
 | 
			
		||||
    ipcMain.on('hide-settings-window', () => windowManager.hideSettingsWindow());
 | 
			
		||||
    ipcMain.on('cancel-hide-settings-window', () => windowManager.cancelHideSettingsWindow());
 | 
			
		||||
    ipcMain.handle('open-login-page', () => windowManager.openLoginPage());
 | 
			
		||||
    ipcMain.handle('move-window-step', (event, direction) => windowManager.moveWindowStep(direction));
 | 
			
		||||
    ipcMain.on('close-shortcut-editor', () => windowManager.closeWindow('shortcut-settings'));
 | 
			
		||||
 | 
			
		||||
    // 기존
 | 
			
		||||
    ipcMain.on('window:hide', (e) => BrowserWindow.fromWebContents(e.sender)?.hide());
 | 
			
		||||
 | 
			
		||||
    // windowManager 관련 추가
 | 
			
		||||
    ipcMain.handle('toggle-content-protection', () => {
 | 
			
		||||
      // windowManager의 toggle-content-protection 로직
 | 
			
		||||
      const newStatus = !getContentProtectionStatus();
 | 
			
		||||
      setContentProtection(newStatus);
 | 
			
		||||
      return newStatus;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ipcMain.handle('resize-header-window', (event, { width, height }) => {
 | 
			
		||||
      const header = windowPool.get('header');
 | 
			
		||||
      if (header) {
 | 
			
		||||
        console.log(`[WindowBridge] Resize request: ${width}x${height}`);
 | 
			
		||||
        
 | 
			
		||||
        // Prevent resizing during animations or if already at target size
 | 
			
		||||
        if (movementManager && movementManager.isAnimating) {
 | 
			
		||||
          console.log('[WindowBridge] Skipping resize during animation');
 | 
			
		||||
          return { success: false, error: 'Cannot resize during animation' };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const currentBounds = header.getBounds();
 | 
			
		||||
        console.log(`[WindowBridge] Current bounds: ${currentBounds.width}x${currentBounds.height} at (${currentBounds.x}, ${currentBounds.y})`);
 | 
			
		||||
        
 | 
			
		||||
        // Skip if already at target size to prevent unnecessary operations
 | 
			
		||||
        if (currentBounds.width === width && currentBounds.height === height) {
 | 
			
		||||
          console.log('[WindowBridge] Already at target size, skipping resize');
 | 
			
		||||
          return { success: true };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const wasResizable = header.isResizable();
 | 
			
		||||
        if (!wasResizable) {
 | 
			
		||||
          header.setResizable(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Calculate the center point of the current window
 | 
			
		||||
        const centerX = currentBounds.x + currentBounds.width / 2;
 | 
			
		||||
        // Calculate new X position to keep the window centered
 | 
			
		||||
        const newX = Math.round(centerX - width / 2);
 | 
			
		||||
 | 
			
		||||
        // Get the current display to ensure we stay within bounds
 | 
			
		||||
        const display = getCurrentDisplay(header);
 | 
			
		||||
        const { x: workAreaX, width: workAreaWidth } = display.workArea;
 | 
			
		||||
        
 | 
			
		||||
        // Clamp the new position to stay within display bounds
 | 
			
		||||
        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);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Update layout after resize
 | 
			
		||||
        if (updateLayout) {
 | 
			
		||||
          updateLayout();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return { success: true };
 | 
			
		||||
      }
 | 
			
		||||
      return { success: false, error: 'Header window not found' };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ipcMain.handle('get-content-protection-status', () => {
 | 
			
		||||
      return getContentProtectionStatus();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ipcMain.handle('open-shortcut-editor', () => {
 | 
			
		||||
      // open-shortcut-editor 로직
 | 
			
		||||
      const header = windowPool.get('header');
 | 
			
		||||
      if (!header) return;
 | 
			
		||||
      globalShortcut.unregisterAll();
 | 
			
		||||
      createFeatureWindows(header, 'shortcut-settings');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // 추가: show-settings-window
 | 
			
		||||
    ipcMain.on('show-settings-window', (event, bounds) => {
 | 
			
		||||
      if (!bounds) return;
 | 
			
		||||
      const win = windowPool.get('settings');
 | 
			
		||||
      if (win && !win.isDestroyed()) {
 | 
			
		||||
        if (settingsHideTimer) {
 | 
			
		||||
          clearTimeout(settingsHideTimer);
 | 
			
		||||
          settingsHideTimer = null;
 | 
			
		||||
        }
 | 
			
		||||
        // 위치 조정 로직
 | 
			
		||||
        const header = windowPool.get('header');
 | 
			
		||||
        const headerBounds = header?.getBounds() ?? { x: 0, y: 0 };
 | 
			
		||||
        const settingsBounds = win.getBounds();
 | 
			
		||||
        const disp = getCurrentDisplay(header);
 | 
			
		||||
        const { x: waX, y: waY, width: waW, height: waH } = disp.workArea;
 | 
			
		||||
        let x = Math.round(headerBounds.x + (bounds?.x ?? 0) + (bounds?.width ?? 0) / 2 - settingsBounds.width / 2);
 | 
			
		||||
        let y = Math.round(headerBounds.y + (bounds?.y ?? 0) + (bounds?.height ?? 0) + 31);
 | 
			
		||||
        x = Math.max(waX + 10, Math.min(waX + waW - settingsBounds.width - 10, x));
 | 
			
		||||
        y = Math.max(waY + 10, Math.min(waY + waH - settingsBounds.height - 10, y));
 | 
			
		||||
        win.setBounds({ x, y });
 | 
			
		||||
        win.__lockedByButton = true;
 | 
			
		||||
        win.show();
 | 
			
		||||
        win.moveTop();
 | 
			
		||||
        win.setAlwaysOnTop(true);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ipcMain.on('hide-settings-window', (event) => {
 | 
			
		||||
      const window = windowPool.get("settings");
 | 
			
		||||
      if (window && !window.isDestroyed()) {
 | 
			
		||||
        if (settingsHideTimer) {
 | 
			
		||||
          clearTimeout(settingsHideTimer);
 | 
			
		||||
        }
 | 
			
		||||
        settingsHideTimer = setTimeout(() => {
 | 
			
		||||
          if (window && !window.isDestroyed()) {
 | 
			
		||||
            window.setAlwaysOnTop(false);
 | 
			
		||||
            window.hide();
 | 
			
		||||
          }
 | 
			
		||||
          settingsHideTimer = null;
 | 
			
		||||
        }, 200);
 | 
			
		||||
        
 | 
			
		||||
        window.__lockedByButton = false;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ipcMain.on('cancel-hide-settings-window', (event) => {
 | 
			
		||||
      if (settingsHideTimer) {
 | 
			
		||||
        clearTimeout(settingsHideTimer);
 | 
			
		||||
        settingsHideTimer = null;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 로그인 페이지 열기
 | 
			
		||||
    ipcMain.handle('open-personalize-page', () => {
 | 
			
		||||
      const webUrl = process.env.pickleglass_WEB_URL || 'http://localhost:3000';
 | 
			
		||||
      const personalizeUrl = `${webUrl}/personalize?desktop=true`;
 | 
			
		||||
      shell.openExternal(personalizeUrl);
 | 
			
		||||
      console.log('Opening personalization page:', personalizeUrl);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 윈도우 이동
 | 
			
		||||
    ipcMain.handle('move-window-step', (event, direction) => {
 | 
			
		||||
      if (movementManager) {
 | 
			
		||||
        movementManager.moveStep(direction);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  // Renderer로 상태를 전송
 | 
			
		||||
  notifyFocusChange(win, isFocused) {
 | 
			
		||||
    win.webContents.send('window:focus-change', isFocused);
 | 
			
		||||
  },
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@ -1,11 +1,24 @@
 | 
			
		||||
const { globalShortcut, screen } = require('electron');
 | 
			
		||||
const shortcutsRepository = require('./repositories');
 | 
			
		||||
const internalBridge = require('../../bridge/internalBridge');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShortcutsService {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.lastVisibleWindows = new Set(['header']);
 | 
			
		||||
        this.mouseEventsIgnored = false;
 | 
			
		||||
        this.movementManager = null;
 | 
			
		||||
        this.windowPool = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initialize(movementManager, windowPool) {
 | 
			
		||||
        this.movementManager = movementManager;
 | 
			
		||||
        this.windowPool = windowPool;
 | 
			
		||||
        internalBridge.on('reregister-shortcuts', () => {
 | 
			
		||||
            console.log('[ShortcutsService] Reregistering shortcuts due to header state change.');
 | 
			
		||||
            this.registerShortcuts();
 | 
			
		||||
        });
 | 
			
		||||
        console.log('[ShortcutsService] Initialized with dependencies and event listener.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getDefaultKeybinds() {
 | 
			
		||||
@ -58,6 +71,32 @@ class ShortcutsService {
 | 
			
		||||
        return keybinds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async handleSaveShortcuts(newKeybinds) {
 | 
			
		||||
        try {
 | 
			
		||||
            await this.saveKeybinds(newKeybinds);
 | 
			
		||||
            const shortcutEditor = this.windowPool.get('shortcut-settings');
 | 
			
		||||
            if (shortcutEditor && !shortcutEditor.isDestroyed()) {
 | 
			
		||||
                shortcutEditor.close(); // This will trigger re-registration on 'closed' event in windowManager
 | 
			
		||||
            } else {
 | 
			
		||||
                // If editor wasn't open, re-register immediately
 | 
			
		||||
                await this.registerShortcuts();
 | 
			
		||||
            }
 | 
			
		||||
            return { success: true };
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            console.error("Failed to save shortcuts:", error);
 | 
			
		||||
            // On failure, re-register old shortcuts to be safe
 | 
			
		||||
            await this.registerShortcuts();
 | 
			
		||||
            return { success: false, error: error.message };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async handleRestoreDefaults() {
 | 
			
		||||
        const defaults = this.getDefaultKeybinds();
 | 
			
		||||
        await this.saveKeybinds(defaults);
 | 
			
		||||
        await this.registerShortcuts();
 | 
			
		||||
        return defaults;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async saveKeybinds(newKeybinds) {
 | 
			
		||||
        const keybindsToSave = [];
 | 
			
		||||
        for (const action in newKeybinds) {
 | 
			
		||||
@ -103,15 +142,19 @@ class ShortcutsService {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async registerShortcuts(movementManager, windowPool) {
 | 
			
		||||
    async registerShortcuts() {
 | 
			
		||||
        if (!this.movementManager || !this.windowPool) {
 | 
			
		||||
            console.error('[Shortcuts] Service not initialized. Cannot register shortcuts.');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const keybinds = await this.loadKeybinds();
 | 
			
		||||
        globalShortcut.unregisterAll();
 | 
			
		||||
        
 | 
			
		||||
        const header = windowPool.get('header');
 | 
			
		||||
        const header = this.windowPool.get('header');
 | 
			
		||||
        const mainWindow = header;
 | 
			
		||||
 | 
			
		||||
        const sendToRenderer = (channel, ...args) => {
 | 
			
		||||
            windowPool.forEach(win => {
 | 
			
		||||
            this.windowPool.forEach(win => {
 | 
			
		||||
                if (win && !win.isDestroyed()) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        win.webContents.send(channel, ...args);
 | 
			
		||||
@ -133,7 +176,7 @@ class ShortcutsService {
 | 
			
		||||
        if (displays.length > 1) {
 | 
			
		||||
            displays.forEach((display, index) => {
 | 
			
		||||
                const key = `${modifier}+Shift+${index + 1}`;
 | 
			
		||||
                globalShortcut.register(key, () => movementManager.moveToDisplay(display.id));
 | 
			
		||||
                globalShortcut.register(key, () => this.movementManager.moveToDisplay(display.id));
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -144,14 +187,14 @@ class ShortcutsService {
 | 
			
		||||
        ];
 | 
			
		||||
        edgeDirections.forEach(({ key, direction }) => {
 | 
			
		||||
            globalShortcut.register(key, () => {
 | 
			
		||||
                if (header && header.isVisible()) movementManager.moveToEdge(direction);
 | 
			
		||||
                if (header && header.isVisible()) this.movementManager.moveToEdge(direction);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // --- User-configurable shortcuts ---
 | 
			
		||||
        if (header?.currentHeaderState === 'apikey') {
 | 
			
		||||
            if (keybinds.toggleVisibility) {
 | 
			
		||||
                globalShortcut.register(keybinds.toggleVisibility, () => this.toggleAllWindowsVisibility(windowPool));
 | 
			
		||||
                globalShortcut.register(keybinds.toggleVisibility, () => this.toggleAllWindowsVisibility(this.windowPool));
 | 
			
		||||
            }
 | 
			
		||||
            console.log('[Shortcuts] ApiKeyHeader is active, only toggleVisibility shortcut is registered.');
 | 
			
		||||
            return;
 | 
			
		||||
@ -164,7 +207,7 @@ class ShortcutsService {
 | 
			
		||||
            let callback;
 | 
			
		||||
            switch(action) {
 | 
			
		||||
                case 'toggleVisibility':
 | 
			
		||||
                    callback = () => this.toggleAllWindowsVisibility(windowPool);
 | 
			
		||||
                    callback = () => this.toggleAllWindowsVisibility(this.windowPool);
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'nextStep':
 | 
			
		||||
                    // Late require to prevent circular dependency
 | 
			
		||||
@ -172,7 +215,7 @@ class ShortcutsService {
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'scrollUp':
 | 
			
		||||
                    callback = () => {
 | 
			
		||||
                        const askWindow = windowPool.get('ask');
 | 
			
		||||
                        const askWindow = this.windowPool.get('ask');
 | 
			
		||||
                        if (askWindow && !askWindow.isDestroyed() && askWindow.isVisible()) {
 | 
			
		||||
                            askWindow.webContents.send('scroll-response-up');
 | 
			
		||||
                        }
 | 
			
		||||
@ -180,23 +223,23 @@ class ShortcutsService {
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'scrollDown':
 | 
			
		||||
                    callback = () => {
 | 
			
		||||
                        const askWindow = windowPool.get('ask');
 | 
			
		||||
                        const askWindow = this.windowPool.get('ask');
 | 
			
		||||
                        if (askWindow && !askWindow.isDestroyed() && askWindow.isVisible()) {
 | 
			
		||||
                            askWindow.webContents.send('scroll-response-down');
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'moveUp':
 | 
			
		||||
                    callback = () => { if (header && header.isVisible()) movementManager.moveStep('up'); };
 | 
			
		||||
                    callback = () => { if (header && header.isVisible()) this.movementManager.moveStep('up'); };
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'moveDown':
 | 
			
		||||
                    callback = () => { if (header && header.isVisible()) movementManager.moveStep('down'); };
 | 
			
		||||
                    callback = () => { if (header && header.isVisible()) this.movementManager.moveStep('down'); };
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'moveLeft':
 | 
			
		||||
                    callback = () => { if (header && header.isVisible()) movementManager.moveStep('left'); };
 | 
			
		||||
                    callback = () => { if (header && header.isVisible()) this.movementManager.moveStep('left'); };
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'moveRight':
 | 
			
		||||
                    callback = () => { if (header && header.isVisible()) movementManager.moveStep('right'); };
 | 
			
		||||
                    callback = () => { if (header && header.isVisible()) this.movementManager.moveStep('right'); };
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'toggleClickThrough':
 | 
			
		||||
                     callback = () => {
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@ const settingsService = require('./features/settings/settingsService');
 | 
			
		||||
const sessionRepository = require('./features/common/repositories/session');
 | 
			
		||||
const modelStateService = require('./features/common/services/modelStateService');
 | 
			
		||||
const featureBridge = require('./bridge/featureBridge');
 | 
			
		||||
const windowBridge = require('./bridge/windowBridge');
 | 
			
		||||
 | 
			
		||||
// Global variables
 | 
			
		||||
const eventBridge = new EventEmitter();
 | 
			
		||||
@ -198,6 +199,7 @@ app.whenReady().then(async () => {
 | 
			
		||||
        //////// after_modelStateService ////////
 | 
			
		||||
 | 
			
		||||
        featureBridge.initialize();  // 추가: featureBridge 초기화
 | 
			
		||||
        windowBridge.initialize();
 | 
			
		||||
        setupWebDataHandlers();
 | 
			
		||||
 | 
			
		||||
        // Initialize Ollama models in database
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ const os = require('os');
 | 
			
		||||
const util = require('util');
 | 
			
		||||
const execFile = util.promisify(require('child_process').execFile);
 | 
			
		||||
const shortcutsService = require('../features/shortcuts/shortcutsService');
 | 
			
		||||
const internalBridge = require('../bridge/internalBridge');
 | 
			
		||||
 | 
			
		||||
// Try to load sharp, but don't fail if it's not available
 | 
			
		||||
let sharp;
 | 
			
		||||
@ -67,8 +68,140 @@ function updateLayout() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let movementManager = null;
 | 
			
		||||
const windowBridge = require('../bridge/windowBridge');
 | 
			
		||||
 | 
			
		||||
const setContentProtection = (status) => {
 | 
			
		||||
    isContentProtectionOn = status;
 | 
			
		||||
    console.log(`[Protection] Content protection toggled to: ${isContentProtectionOn}`);
 | 
			
		||||
    windowPool.forEach(win => {
 | 
			
		||||
        if (win && !win.isDestroyed()) {
 | 
			
		||||
            win.setContentProtection(isContentProtectionOn);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getContentProtectionStatus = () => isContentProtectionOn;
 | 
			
		||||
 | 
			
		||||
const toggleContentProtection = () => {
 | 
			
		||||
    const newStatus = !getContentProtectionStatus();
 | 
			
		||||
    setContentProtection(newStatus);
 | 
			
		||||
    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 openShortcutEditor = () => {
 | 
			
		||||
    const header = windowPool.get('header');
 | 
			
		||||
    if (!header) return;
 | 
			
		||||
    globalShortcut.unregisterAll();
 | 
			
		||||
    createFeatureWindows(header, 'shortcut-settings');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showSettingsWindow = (bounds) => {
 | 
			
		||||
    if (!bounds) return;
 | 
			
		||||
    const win = windowPool.get('settings');
 | 
			
		||||
    if (win && !win.isDestroyed()) {
 | 
			
		||||
      if (settingsHideTimer) {
 | 
			
		||||
        clearTimeout(settingsHideTimer);
 | 
			
		||||
        settingsHideTimer = null;
 | 
			
		||||
      }
 | 
			
		||||
      const header = windowPool.get('header');
 | 
			
		||||
      const headerBounds = header?.getBounds() ?? { x: 0, y: 0 };
 | 
			
		||||
      const settingsBounds = win.getBounds();
 | 
			
		||||
      const disp = getCurrentDisplay(header);
 | 
			
		||||
      const { x: waX, y: waY, width: waW, height: waH } = disp.workArea;
 | 
			
		||||
      let x = Math.round(headerBounds.x + (bounds?.x ?? 0) + (bounds?.width ?? 0) / 2 - settingsBounds.width / 2);
 | 
			
		||||
      let y = Math.round(headerBounds.y + (bounds?.y ?? 0) + (bounds?.height ?? 0) + 31);
 | 
			
		||||
      x = Math.max(waX + 10, Math.min(waX + waW - settingsBounds.width - 10, x));
 | 
			
		||||
      y = Math.max(waY + 10, Math.min(waY + waH - settingsBounds.height - 10, y));
 | 
			
		||||
      win.setBounds({ x, y });
 | 
			
		||||
      win.__lockedByButton = true;
 | 
			
		||||
      win.show();
 | 
			
		||||
      win.moveTop();
 | 
			
		||||
      win.setAlwaysOnTop(true);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const hideSettingsWindow = () => {
 | 
			
		||||
    const window = windowPool.get("settings");
 | 
			
		||||
    if (window && !window.isDestroyed()) {
 | 
			
		||||
      if (settingsHideTimer) {
 | 
			
		||||
        clearTimeout(settingsHideTimer);
 | 
			
		||||
      }
 | 
			
		||||
      settingsHideTimer = setTimeout(() => {
 | 
			
		||||
        if (window && !window.isDestroyed()) {
 | 
			
		||||
          window.setAlwaysOnTop(false);
 | 
			
		||||
          window.hide();
 | 
			
		||||
        }
 | 
			
		||||
        settingsHideTimer = null;
 | 
			
		||||
      }, 200);
 | 
			
		||||
      
 | 
			
		||||
      window.__lockedByButton = false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cancelHideSettingsWindow = () => {
 | 
			
		||||
    if (settingsHideTimer) {
 | 
			
		||||
      clearTimeout(settingsHideTimer);
 | 
			
		||||
      settingsHideTimer = null;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const openLoginPage = () => {
 | 
			
		||||
    const webUrl = process.env.pickleglass_WEB_URL || 'http://localhost:3000';
 | 
			
		||||
    const personalizeUrl = `${webUrl}/personalize?desktop=true`;
 | 
			
		||||
    shell.openExternal(personalizeUrl);
 | 
			
		||||
    console.log('Opening personalization page:', personalizeUrl);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const moveWindowStep = (direction) => {
 | 
			
		||||
    if (movementManager) {
 | 
			
		||||
        movementManager.moveStep(direction);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function createFeatureWindows(header, namesToCreate) {
 | 
			
		||||
@ -255,7 +388,7 @@ function createFeatureWindows(header, namesToCreate) {
 | 
			
		||||
                    restoreClicks();
 | 
			
		||||
                    windowPool.delete('shortcut-settings');
 | 
			
		||||
                    console.log('[Shortcuts] Re-enabled after editing.');
 | 
			
		||||
                    shortcutsService.registerShortcuts(movementManager, windowPool);
 | 
			
		||||
                    shortcutsService.registerShortcuts();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                shortcutEditor.webContents.once('dom-ready', async () => {
 | 
			
		||||
@ -408,25 +541,11 @@ function createWindows() {
 | 
			
		||||
    layoutManager = new WindowLayoutManager(windowPool);
 | 
			
		||||
 | 
			
		||||
    header.webContents.once('dom-ready', () => {
 | 
			
		||||
        shortcutsService.registerShortcuts(movementManager, windowPool);
 | 
			
		||||
        shortcutsService.initialize(movementManager, windowPool);
 | 
			
		||||
        shortcutsService.registerShortcuts();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    setupIpcHandlers(movementManager);
 | 
			
		||||
    
 | 
			
		||||
    // Content protection helper functions
 | 
			
		||||
    const getContentProtectionStatus = () => isContentProtectionOn;
 | 
			
		||||
    const setContentProtection = (status) => {
 | 
			
		||||
        isContentProtectionOn = status;
 | 
			
		||||
        console.log(`[Protection] Content protection toggled to: ${isContentProtectionOn}`);
 | 
			
		||||
        windowPool.forEach(win => {
 | 
			
		||||
            if (win && !win.isDestroyed()) {
 | 
			
		||||
                win.setContentProtection(isContentProtectionOn);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    // Initialize windowBridge with required dependencies
 | 
			
		||||
    windowBridge.initialize(windowPool, require('electron').app, require('electron').shell, getCurrentDisplay, createFeatureWindows, movementManager, getContentProtectionStatus, setContentProtection, updateLayout);
 | 
			
		||||
 | 
			
		||||
    if (currentHeaderState === 'main') {
 | 
			
		||||
        createFeatureWindows(header, ['listen', 'ask', 'settings', 'shortcut-settings']);
 | 
			
		||||
@ -499,46 +618,7 @@ function setupIpcHandlers(movementManager) {
 | 
			
		||||
        } else {         // 'apikey' | 'permission'
 | 
			
		||||
            destroyFeatureWindows();
 | 
			
		||||
        }
 | 
			
		||||
        shortcutsService.registerShortcuts(movementManager, windowPool);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ipcMain.handle('get-current-shortcuts', async () => {
 | 
			
		||||
        return await shortcutsService.loadKeybinds();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ipcMain.handle('get-default-shortcuts', async () => {
 | 
			
		||||
        const defaults = shortcutsService.getDefaultKeybinds();
 | 
			
		||||
        await shortcutsService.saveKeybinds(defaults);
 | 
			
		||||
        // Reregister shortcuts with new defaults
 | 
			
		||||
        await shortcutsService.registerShortcuts(movementManager, windowPool);
 | 
			
		||||
        return defaults;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ipcMain.handle('save-shortcuts', async (event, newKeybinds) => {
 | 
			
		||||
        try {
 | 
			
		||||
            await shortcutsService.saveKeybinds(newKeybinds);
 | 
			
		||||
            
 | 
			
		||||
            const editor = windowPool.get('shortcut-settings');
 | 
			
		||||
            if (editor && !editor.isDestroyed()) {
 | 
			
		||||
                editor.close(); // This will trigger re-registration on 'closed' event
 | 
			
		||||
            } else {
 | 
			
		||||
                // If editor wasn't open, re-register immediately
 | 
			
		||||
                await shortcutsService.registerShortcuts(movementManager, windowPool);
 | 
			
		||||
            }
 | 
			
		||||
            return { success: true };
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            console.error("Failed to save shortcuts:", error);
 | 
			
		||||
            // On failure, re-register old shortcuts to be safe
 | 
			
		||||
            await shortcutsService.registerShortcuts(movementManager, windowPool);
 | 
			
		||||
            return { success: false, error: error.message };
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ipcMain.on('close-shortcut-editor', () => {
 | 
			
		||||
        const editor = windowPool.get('shortcut-settings');
 | 
			
		||||
        if (editor && !editor.isDestroyed()) {
 | 
			
		||||
            editor.close();
 | 
			
		||||
        }
 | 
			
		||||
        internalBridge.emit('reregister-shortcuts');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // resize-header-window handler moved to windowBridge.js to avoid duplication
 | 
			
		||||
@ -1063,6 +1143,13 @@ async function captureScreenshot(options = {}) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const closeWindow = (windowName) => {
 | 
			
		||||
    const win = windowPool.get(windowName);
 | 
			
		||||
    if (win && !win.isDestroyed()) {
 | 
			
		||||
        win.close();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    updateLayout,
 | 
			
		||||
    createWindows,
 | 
			
		||||
@ -1073,4 +1160,14 @@ module.exports = {
 | 
			
		||||
    getCurrentModelInfo,
 | 
			
		||||
    captureScreenshot,
 | 
			
		||||
    toggleFeature, // Export toggleFeature so shortcutsService can use it
 | 
			
		||||
    toggleContentProtection,
 | 
			
		||||
    resizeHeaderWindow,
 | 
			
		||||
    getContentProtectionStatus,
 | 
			
		||||
    openShortcutEditor,
 | 
			
		||||
    showSettingsWindow,
 | 
			
		||||
    hideSettingsWindow,
 | 
			
		||||
    cancelHideSettingsWindow,
 | 
			
		||||
    openLoginPage,
 | 
			
		||||
    moveWindowStep,
 | 
			
		||||
    closeWindow,
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user