centralized window layout/movement feature to windowmanager
This commit is contained in:
		
							parent
							
								
									94ae002d83
								
							
						
					
					
						commit
						4d93df09e2
					
				@ -29,9 +29,12 @@ module.exports = {
 | 
				
			|||||||
    ipcMain.handle('settings:shutdown-ollama', async () => await settingsService.shutdownOllama());
 | 
					    ipcMain.handle('settings:shutdown-ollama', async () => await settingsService.shutdownOllama());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Shortcuts
 | 
					    // Shortcuts
 | 
				
			||||||
    ipcMain.handle('get-current-shortcuts', async () => await shortcutsService.loadKeybinds());
 | 
					    ipcMain.handle('settings:getCurrentShortcuts', async () => await shortcutsService.loadKeybinds());
 | 
				
			||||||
    ipcMain.handle('get-default-shortcuts', async () => await shortcutsService.handleRestoreDefaults());
 | 
					    ipcMain.handle('shortcut:getDefaultShortcuts', async () => await shortcutsService.handleRestoreDefaults());
 | 
				
			||||||
    ipcMain.handle('save-shortcuts', async (event, newKeybinds) => await shortcutsService.handleSaveShortcuts(newKeybinds));
 | 
					    ipcMain.handle('shortcut:closeShortcutSettingsWindow', async () => await shortcutsService.closeShortcutSettingsWindow());
 | 
				
			||||||
 | 
					    ipcMain.handle('shortcut:openShortcutSettingsWindow', async () => await shortcutsService.openShortcutSettingsWindow());
 | 
				
			||||||
 | 
					    ipcMain.handle('shortcut:saveShortcuts', async (event, newKeybinds) => await shortcutsService.handleSaveShortcuts(newKeybinds));
 | 
				
			||||||
 | 
					    ipcMain.handle('shortcut:toggleAllWindowsVisibility', async () => await shortcutsService.toggleAllWindowsVisibility());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Permissions
 | 
					    // Permissions
 | 
				
			||||||
    ipcMain.handle('check-system-permissions', async () => await permissionService.checkSystemPermissions());
 | 
					    ipcMain.handle('check-system-permissions', async () => await permissionService.checkSystemPermissions());
 | 
				
			||||||
 | 
				
			|||||||
@ -7,14 +7,13 @@ module.exports = {
 | 
				
			|||||||
    ipcMain.handle('toggle-content-protection', () => windowManager.toggleContentProtection());
 | 
					    ipcMain.handle('toggle-content-protection', () => windowManager.toggleContentProtection());
 | 
				
			||||||
    ipcMain.handle('resize-header-window', (event, args) => windowManager.resizeHeaderWindow(args));
 | 
					    ipcMain.handle('resize-header-window', (event, args) => windowManager.resizeHeaderWindow(args));
 | 
				
			||||||
    ipcMain.handle('get-content-protection-status', () => windowManager.getContentProtectionStatus());
 | 
					    ipcMain.handle('get-content-protection-status', () => windowManager.getContentProtectionStatus());
 | 
				
			||||||
    ipcMain.handle('open-shortcut-editor', () => windowManager.openShortcutEditor());
 | 
					    ipcMain.on('show-settings-window', () => windowManager.showSettingsWindow());
 | 
				
			||||||
    ipcMain.on('show-settings-window', (event, bounds) => windowManager.showSettingsWindow(bounds));
 | 
					 | 
				
			||||||
    ipcMain.on('hide-settings-window', () => windowManager.hideSettingsWindow());
 | 
					    ipcMain.on('hide-settings-window', () => windowManager.hideSettingsWindow());
 | 
				
			||||||
    ipcMain.on('cancel-hide-settings-window', () => windowManager.cancelHideSettingsWindow());
 | 
					    ipcMain.on('cancel-hide-settings-window', () => windowManager.cancelHideSettingsWindow());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.handle('open-login-page', () => windowManager.openLoginPage());
 | 
					    ipcMain.handle('open-login-page', () => windowManager.openLoginPage());
 | 
				
			||||||
    ipcMain.handle('open-personalize-page', () => windowManager.openLoginPage());
 | 
					    ipcMain.handle('open-personalize-page', () => windowManager.openLoginPage());
 | 
				
			||||||
    ipcMain.handle('move-window-step', (event, direction) => windowManager.moveWindowStep(direction));
 | 
					    ipcMain.handle('move-window-step', (event, direction) => windowManager.moveWindowStep(direction));
 | 
				
			||||||
    ipcMain.on('close-shortcut-editor', () => windowManager.closeWindow('shortcut-settings'));
 | 
					 | 
				
			||||||
    ipcMain.handle('open-external', (event, url) => shell.openExternal(url));
 | 
					    ipcMain.handle('open-external', (event, url) => shell.openExternal(url));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Newly moved handlers from windowManager
 | 
					    // Newly moved handlers from windowManager
 | 
				
			||||||
@ -24,9 +23,6 @@ module.exports = {
 | 
				
			|||||||
    ipcMain.handle('move-header', (event, newX, newY) => windowManager.moveHeader(newX, newY));
 | 
					    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('move-header-to', (event, newX, newY) => windowManager.moveHeaderTo(newX, newY));
 | 
				
			||||||
    ipcMain.handle('adjust-window-height', (event, targetHeight) => windowManager.adjustWindowHeight(event.sender, targetHeight));
 | 
					    ipcMain.handle('adjust-window-height', (event, targetHeight) => windowManager.adjustWindowHeight(event.sender, targetHeight));
 | 
				
			||||||
    ipcMain.handle('toggle-all-windows-visibility', () => windowManager.toggleAllWindowsVisibility());
 | 
					 | 
				
			||||||
    // ipcMain.on('animation-finished', (event) => windowManager.handleAnimationFinished(event.sender));
 | 
					 | 
				
			||||||
    // ipcMain.handle('ask:closeAskWindow', () => windowManager.closeAskWindow());
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  notifyFocusChange(win, isFocused) {
 | 
					  notifyFocusChange(win, isFocused) {
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@ const { createStreamingLLM } = require('../common/ai/factory');
 | 
				
			|||||||
// Lazy require helper to avoid circular dependency issues
 | 
					// Lazy require helper to avoid circular dependency issues
 | 
				
			||||||
const getWindowManager = () => require('../../window/windowManager');
 | 
					const getWindowManager = () => require('../../window/windowManager');
 | 
				
			||||||
const internalBridge = require('../../bridge/internalBridge');
 | 
					const internalBridge = require('../../bridge/internalBridge');
 | 
				
			||||||
const { EVENTS } = internalBridge;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getWindowPool = () => {
 | 
					const getWindowPool = () => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
@ -162,11 +161,11 @@ class AskService {
 | 
				
			|||||||
            this._broadcastState();
 | 
					            this._broadcastState();
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            if (askWindow && askWindow.isVisible()) {
 | 
					            if (askWindow && askWindow.isVisible()) {
 | 
				
			||||||
                internalBridge.emit('request-window-visibility', { name: 'ask', visible: false });
 | 
					                internalBridge.emit('window:requestVisibility', { name: 'ask', visible: false });
 | 
				
			||||||
                this.state.isVisible = false;
 | 
					                this.state.isVisible = false;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                console.log('[AskService] Showing hidden Ask window');
 | 
					                console.log('[AskService] Showing hidden Ask window');
 | 
				
			||||||
                internalBridge.emit('request-window-visibility', { name: 'ask', visible: true });
 | 
					                internalBridge.emit('window:requestVisibility', { name: 'ask', visible: true });
 | 
				
			||||||
                this.state.isVisible = true;
 | 
					                this.state.isVisible = true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (this.state.isVisible) {
 | 
					            if (this.state.isVisible) {
 | 
				
			||||||
@ -192,7 +191,7 @@ class AskService {
 | 
				
			|||||||
            };
 | 
					            };
 | 
				
			||||||
            this._broadcastState();
 | 
					            this._broadcastState();
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
            internalBridge.emit('request-window-visibility', { name: 'ask', visible: false });
 | 
					            internalBridge.emit('window:requestVisibility', { name: 'ask', visible: false });
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
            return { success: true };
 | 
					            return { success: true };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -217,7 +216,16 @@ class AskService {
 | 
				
			|||||||
     * @returns {Promise<{success: boolean, response?: string, error?: string}>}
 | 
					     * @returns {Promise<{success: boolean, response?: string, error?: string}>}
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async sendMessage(userPrompt, conversationHistoryRaw=[]) {
 | 
					    async sendMessage(userPrompt, conversationHistoryRaw=[]) {
 | 
				
			||||||
        // ensureAskWindowVisible();
 | 
					        internalBridge.emit('window:requestVisibility', { name: 'ask', visible: true });
 | 
				
			||||||
 | 
					        this.state = {
 | 
				
			||||||
 | 
					            ...this.state,
 | 
				
			||||||
 | 
					            isLoading: true,
 | 
				
			||||||
 | 
					            isStreaming: false,
 | 
				
			||||||
 | 
					            currentQuestion: userPrompt,
 | 
				
			||||||
 | 
					            currentResponse: '',
 | 
				
			||||||
 | 
					            showTextInput: false,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        this._broadcastState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.abortController) {
 | 
					        if (this.abortController) {
 | 
				
			||||||
            this.abortController.abort('New request received.');
 | 
					            this.abortController.abort('New request received.');
 | 
				
			||||||
@ -226,26 +234,10 @@ class AskService {
 | 
				
			|||||||
        const { signal } = this.abortController;
 | 
					        const { signal } = this.abortController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // if (!userPrompt || userPrompt.trim().length === 0) {
 | 
					 | 
				
			||||||
        //     console.warn('[AskService] Cannot process empty message');
 | 
					 | 
				
			||||||
        //     return { success: false, error: 'Empty message' };
 | 
					 | 
				
			||||||
        // }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let sessionId;
 | 
					        let sessionId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            console.log(`[AskService] 🤖 Processing message: ${userPrompt.substring(0, 50)}...`);
 | 
					            console.log(`[AskService] 🤖 Processing message: ${userPrompt.substring(0, 50)}...`);
 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            this.state = {
 | 
					 | 
				
			||||||
                ...this.state,
 | 
					 | 
				
			||||||
                isLoading: true,
 | 
					 | 
				
			||||||
                isStreaming: false,
 | 
					 | 
				
			||||||
                currentQuestion: userPrompt,
 | 
					 | 
				
			||||||
                currentResponse: '',
 | 
					 | 
				
			||||||
                showTextInput: false,
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            this._broadcastState();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            sessionId = await sessionRepository.getOrCreateActive('ask');
 | 
					            sessionId = await sessionRepository.getOrCreateActive('ask');
 | 
				
			||||||
            await askRepository.addAiMessage({ sessionId, role: 'user', content: userPrompt.trim() });
 | 
					            await askRepository.addAiMessage({ sessionId, role: 'user', content: userPrompt.trim() });
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,6 @@ const authService = require('../common/services/authService');
 | 
				
			|||||||
const sessionRepository = require('../common/repositories/session');
 | 
					const sessionRepository = require('../common/repositories/session');
 | 
				
			||||||
const sttRepository = require('./stt/repositories');
 | 
					const sttRepository = require('./stt/repositories');
 | 
				
			||||||
const internalBridge = require('../../bridge/internalBridge');
 | 
					const internalBridge = require('../../bridge/internalBridge');
 | 
				
			||||||
const { EVENTS } = internalBridge;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ListenService {
 | 
					class ListenService {
 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
@ -109,7 +108,7 @@ class ListenService {
 | 
				
			|||||||
            switch (listenButtonText) {
 | 
					            switch (listenButtonText) {
 | 
				
			||||||
                case 'Listen':
 | 
					                case 'Listen':
 | 
				
			||||||
                    console.log('[ListenService] changeSession to "Listen"');
 | 
					                    console.log('[ListenService] changeSession to "Listen"');
 | 
				
			||||||
                    internalBridge.emit('request-window-visibility', { name: 'listen', visible: true });
 | 
					                    internalBridge.emit('window:requestVisibility', { name: 'listen', visible: true });
 | 
				
			||||||
                    await this.initializeSession();
 | 
					                    await this.initializeSession();
 | 
				
			||||||
                    listenWindow.webContents.send('session-state-changed', { isActive: true });
 | 
					                    listenWindow.webContents.send('session-state-changed', { isActive: true });
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
@ -122,7 +121,7 @@ class ListenService {
 | 
				
			|||||||
        
 | 
					        
 | 
				
			||||||
                case 'Done':
 | 
					                case 'Done':
 | 
				
			||||||
                    console.log('[ListenService] changeSession to "Done"');
 | 
					                    console.log('[ListenService] changeSession to "Done"');
 | 
				
			||||||
                    internalBridge.emit('request-window-visibility', { name: 'listen', visible: false });
 | 
					                    internalBridge.emit('window:requestVisibility', { name: 'listen', visible: false });
 | 
				
			||||||
                    listenWindow.webContents.send('session-state-changed', { isActive: false });
 | 
					                    listenWindow.webContents.send('session-state-changed', { isActive: false });
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@ class ShortcutsService {
 | 
				
			|||||||
        this.mouseEventsIgnored = false;
 | 
					        this.mouseEventsIgnored = false;
 | 
				
			||||||
        this.movementManager = null;
 | 
					        this.movementManager = null;
 | 
				
			||||||
        this.windowPool = null;
 | 
					        this.windowPool = null;
 | 
				
			||||||
 | 
					        this.allWindowVisibility = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    initialize(movementManager, windowPool) {
 | 
					    initialize(movementManager, windowPool) {
 | 
				
			||||||
@ -22,6 +23,41 @@ class ShortcutsService {
 | 
				
			|||||||
        console.log('[ShortcutsService] Initialized with dependencies and event listener.');
 | 
					        console.log('[ShortcutsService] Initialized with dependencies and event listener.');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async openShortcutSettingsWindow () {
 | 
				
			||||||
 | 
					        const keybinds = await this.loadKeybinds();
 | 
				
			||||||
 | 
					        const shortcutWin = this.windowPool.get('shortcut-settings');
 | 
				
			||||||
 | 
					        shortcutWin.webContents.send('shortcut:loadShortcuts', keybinds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        globalShortcut.unregisterAll();
 | 
				
			||||||
 | 
					        internalBridge.emit('window:requestVisibility', { name: 'shortcut-settings', visible: true });
 | 
				
			||||||
 | 
					        console.log('[ShortcutsService] Shortcut settings window opened.');
 | 
				
			||||||
 | 
					        return { success: true };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async closeShortcutSettingsWindow () {
 | 
				
			||||||
 | 
					        await this.registerShortcuts();
 | 
				
			||||||
 | 
					        internalBridge.emit('window:requestVisibility', { name: 'shortcut-settings', visible: false });
 | 
				
			||||||
 | 
					        console.log('[ShortcutsService] Shortcut settings window closed.');
 | 
				
			||||||
 | 
					        return { success: true };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async handleSaveShortcuts(newKeybinds) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await this.saveKeybinds(newKeybinds);
 | 
				
			||||||
 | 
					            await this.closeShortcutSettingsWindow();
 | 
				
			||||||
 | 
					            return { success: true };
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            console.error("Failed to save shortcuts:", error);
 | 
				
			||||||
 | 
					            await this.closeShortcutSettingsWindow();
 | 
				
			||||||
 | 
					            return { success: false, error: error.message };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async handleRestoreDefaults() {
 | 
				
			||||||
 | 
					        const defaults = this.getDefaultKeybinds();
 | 
				
			||||||
 | 
					        return defaults;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getDefaultKeybinds() {
 | 
					    getDefaultKeybinds() {
 | 
				
			||||||
        const isMac = process.platform === 'darwin';
 | 
					        const isMac = process.platform === 'darwin';
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
@ -72,32 +108,6 @@ class ShortcutsService {
 | 
				
			|||||||
        return keybinds;
 | 
					        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) {
 | 
					    async saveKeybinds(newKeybinds) {
 | 
				
			||||||
        const keybindsToSave = [];
 | 
					        const keybindsToSave = [];
 | 
				
			||||||
        for (const action in newKeybinds) {
 | 
					        for (const action in newKeybinds) {
 | 
				
			||||||
@ -112,38 +122,22 @@ class ShortcutsService {
 | 
				
			|||||||
        console.log(`[Shortcuts] Saved keybinds.`);
 | 
					        console.log(`[Shortcuts] Saved keybinds.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    toggleAllWindowsVisibility(windowPool) {
 | 
					    async toggleAllWindowsVisibility() {
 | 
				
			||||||
        const header = windowPool.get('header');
 | 
					        const targetVisibility = !this.allWindowVisibility;
 | 
				
			||||||
        if (!header) return;
 | 
					        internalBridge.emit('window:requestToggleAllWindowsVisibility', {
 | 
				
			||||||
      
 | 
					            targetVisibility: targetVisibility
 | 
				
			||||||
        if (header.isVisible()) {
 | 
					 | 
				
			||||||
            this.lastVisibleWindows.clear();
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
            windowPool.forEach((win, name) => {
 | 
					 | 
				
			||||||
                if (win && !win.isDestroyed() && win.isVisible()) {
 | 
					 | 
				
			||||||
                    this.lastVisibleWindows.add(name);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
            this.lastVisibleWindows.forEach(name => {
 | 
					 | 
				
			||||||
                if (name === 'header') return;
 | 
					 | 
				
			||||||
                const win = windowPool.get(name);
 | 
					 | 
				
			||||||
                if (win && !win.isDestroyed()) win.hide();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            header.hide();
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
        this.lastVisibleWindows.forEach(name => {
 | 
					 | 
				
			||||||
            const win = windowPool.get(name);
 | 
					 | 
				
			||||||
            if (win && !win.isDestroyed()) {
 | 
					 | 
				
			||||||
                win.show();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.allWindowVisibility) {
 | 
				
			||||||
 | 
					            await this.registerShortcuts(true);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            await this.registerShortcuts();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.allWindowVisibility = !this.allWindowVisibility;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async registerShortcuts() {
 | 
					    async registerShortcuts(registerOnlyToggleVisibility = false) {
 | 
				
			||||||
        if (!this.movementManager || !this.windowPool) {
 | 
					        if (!this.movementManager || !this.windowPool) {
 | 
				
			||||||
            console.error('[Shortcuts] Service not initialized. Cannot register shortcuts.');
 | 
					            console.error('[Shortcuts] Service not initialized. Cannot register shortcuts.');
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@ -168,6 +162,14 @@ class ShortcutsService {
 | 
				
			|||||||
        
 | 
					        
 | 
				
			||||||
        sendToRenderer('shortcuts-updated', keybinds);
 | 
					        sendToRenderer('shortcuts-updated', keybinds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (registerOnlyToggleVisibility) {
 | 
				
			||||||
 | 
					            if (keybinds.toggleVisibility) {
 | 
				
			||||||
 | 
					                globalShortcut.register(keybinds.toggleVisibility, () => this.toggleAllWindowsVisibility());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            console.log('[Shortcuts] registerOnlyToggleVisibility, only toggleVisibility shortcut is registered.');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // --- Hardcoded shortcuts ---
 | 
					        // --- Hardcoded shortcuts ---
 | 
				
			||||||
        const isMac = process.platform === 'darwin';
 | 
					        const isMac = process.platform === 'darwin';
 | 
				
			||||||
        const modifier = isMac ? 'Cmd' : 'Ctrl';
 | 
					        const modifier = isMac ? 'Cmd' : 'Ctrl';
 | 
				
			||||||
@ -195,7 +197,7 @@ class ShortcutsService {
 | 
				
			|||||||
        // --- User-configurable shortcuts ---
 | 
					        // --- User-configurable shortcuts ---
 | 
				
			||||||
        if (header?.currentHeaderState === 'apikey') {
 | 
					        if (header?.currentHeaderState === 'apikey') {
 | 
				
			||||||
            if (keybinds.toggleVisibility) {
 | 
					            if (keybinds.toggleVisibility) {
 | 
				
			||||||
                globalShortcut.register(keybinds.toggleVisibility, () => this.toggleAllWindowsVisibility(this.windowPool));
 | 
					                globalShortcut.register(keybinds.toggleVisibility, () => this.toggleAllWindowsVisibility());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            console.log('[Shortcuts] ApiKeyHeader is active, only toggleVisibility shortcut is registered.');
 | 
					            console.log('[Shortcuts] ApiKeyHeader is active, only toggleVisibility shortcut is registered.');
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@ -208,7 +210,7 @@ class ShortcutsService {
 | 
				
			|||||||
            let callback;
 | 
					            let callback;
 | 
				
			||||||
            switch(action) {
 | 
					            switch(action) {
 | 
				
			||||||
                case 'toggleVisibility':
 | 
					                case 'toggleVisibility':
 | 
				
			||||||
                    callback = () => this.toggleAllWindowsVisibility(this.windowPool);
 | 
					                    callback = () => this.toggleAllWindowsVisibility();
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                case 'nextStep':
 | 
					                case 'nextStep':
 | 
				
			||||||
                    callback = () => askService.toggleAskButton(true);
 | 
					                    callback = () => askService.toggleAskButton(true);
 | 
				
			||||||
@ -282,4 +284,7 @@ class ShortcutsService {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = new ShortcutsService(); 
 | 
					
 | 
				
			||||||
 | 
					const shortcutsService = new ShortcutsService();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = shortcutsService;
 | 
				
			||||||
@ -95,11 +95,14 @@ contextBridge.exposeInMainWorld('api', {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Settings Window Management
 | 
					    // Settings Window Management
 | 
				
			||||||
    cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
 | 
					    cancelHideSettingsWindow: () => ipcRenderer.send('cancel-hide-settings-window'),
 | 
				
			||||||
    showSettingsWindow: (bounds) => ipcRenderer.send('show-settings-window', bounds),
 | 
					    showSettingsWindow: () => ipcRenderer.send('show-settings-window'),
 | 
				
			||||||
    hideSettingsWindow: () => ipcRenderer.send('hide-settings-window'),
 | 
					    hideSettingsWindow: () => ipcRenderer.send('hide-settings-window'),
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Generic invoke (for dynamic channel names)
 | 
					    // Generic invoke (for dynamic channel names)
 | 
				
			||||||
    invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),
 | 
					    // invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),
 | 
				
			||||||
 | 
					    sendListenButtonClick: (listenButtonText) => ipcRenderer.invoke('listen:changeSession', listenButtonText),
 | 
				
			||||||
 | 
					    sendAskButtonClick: () => ipcRenderer.invoke('ask:toggleAskButton'),
 | 
				
			||||||
 | 
					    sendToggleAllWindowsVisibility: () => ipcRenderer.invoke('shortcut:toggleAllWindowsVisibility'),
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Listeners
 | 
					    // Listeners
 | 
				
			||||||
    onListenChangeSessionResult: (callback) => ipcRenderer.on('listen:changeSessionResult', callback),
 | 
					    onListenChangeSessionResult: (callback) => ipcRenderer.on('listen:changeSessionResult', callback),
 | 
				
			||||||
@ -213,8 +216,8 @@ contextBridge.exposeInMainWorld('api', {
 | 
				
			|||||||
    setAutoUpdate: (isEnabled) => ipcRenderer.invoke('settings:set-auto-update', isEnabled),
 | 
					    setAutoUpdate: (isEnabled) => ipcRenderer.invoke('settings:set-auto-update', isEnabled),
 | 
				
			||||||
    getContentProtectionStatus: () => ipcRenderer.invoke('get-content-protection-status'),
 | 
					    getContentProtectionStatus: () => ipcRenderer.invoke('get-content-protection-status'),
 | 
				
			||||||
    toggleContentProtection: () => ipcRenderer.invoke('toggle-content-protection'),
 | 
					    toggleContentProtection: () => ipcRenderer.invoke('toggle-content-protection'),
 | 
				
			||||||
    getCurrentShortcuts: () => ipcRenderer.invoke('get-current-shortcuts'),
 | 
					    getCurrentShortcuts: () => ipcRenderer.invoke('settings:getCurrentShortcuts'),
 | 
				
			||||||
    openShortcutEditor: () => ipcRenderer.invoke('open-shortcut-editor'),
 | 
					    openShortcutSettingsWindow: () => ipcRenderer.invoke('shortcut:openShortcutSettingsWindow'),
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Window Management
 | 
					    // Window Management
 | 
				
			||||||
    moveWindowStep: (direction) => ipcRenderer.invoke('move-window-step', direction),
 | 
					    moveWindowStep: (direction) => ipcRenderer.invoke('move-window-step', direction),
 | 
				
			||||||
@ -245,20 +248,17 @@ contextBridge.exposeInMainWorld('api', {
 | 
				
			|||||||
  // src/ui/settings/ShortCutSettingsView.js
 | 
					  // src/ui/settings/ShortCutSettingsView.js
 | 
				
			||||||
  shortcutSettingsView: {
 | 
					  shortcutSettingsView: {
 | 
				
			||||||
    // Shortcut Management
 | 
					    // Shortcut Management
 | 
				
			||||||
    saveShortcuts: (shortcuts) => ipcRenderer.invoke('save-shortcuts', shortcuts),
 | 
					    saveShortcuts: (shortcuts) => ipcRenderer.invoke('shortcut:saveShortcuts', shortcuts),
 | 
				
			||||||
    getDefaultShortcuts: () => ipcRenderer.invoke('get-default-shortcuts'),
 | 
					    getDefaultShortcuts: () => ipcRenderer.invoke('shortcut:getDefaultShortcuts'),
 | 
				
			||||||
    closeShortcutEditor: () => ipcRenderer.send('close-shortcut-editor'),
 | 
					    closeShortcutSettingsWindow: () => ipcRenderer.invoke('shortcut:closeShortcutSettingsWindow'),
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Listeners
 | 
					    // Listeners
 | 
				
			||||||
    onLoadShortcuts: (callback) => ipcRenderer.on('load-shortcuts', callback),
 | 
					    onLoadShortcuts: (callback) => ipcRenderer.on('shortcut:loadShortcuts', callback),
 | 
				
			||||||
    removeOnLoadShortcuts: (callback) => ipcRenderer.removeListener('load-shortcuts', callback)
 | 
					    removeOnLoadShortcuts: (callback) => ipcRenderer.removeListener('shortcut:loadShortcuts', callback)
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // src/ui/app/content.html inline scripts
 | 
					  // src/ui/app/content.html inline scripts
 | 
				
			||||||
  content: {
 | 
					  content: {
 | 
				
			||||||
    // Animation Management
 | 
					 | 
				
			||||||
    // sendAnimationFinished: () => ipcRenderer.send('animation-finished'),
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // Listeners
 | 
					    // Listeners
 | 
				
			||||||
    onSettingsWindowHideAnimation: (callback) => ipcRenderer.on('settings-window-hide-animation', callback),
 | 
					    onSettingsWindowHideAnimation: (callback) => ipcRenderer.on('settings-window-hide-animation', callback),
 | 
				
			||||||
    removeOnSettingsWindowHideAnimation: (callback) => ipcRenderer.removeListener('settings-window-hide-animation', callback),    
 | 
					    removeOnSettingsWindowHideAnimation: (callback) => ipcRenderer.removeListener('settings-window-hide-animation', callback),    
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,6 @@ import { html, css, LitElement } from '../assets/lit-core-2.7.4.min.js';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export class MainHeader extends LitElement {
 | 
					export class MainHeader extends LitElement {
 | 
				
			||||||
    static properties = {
 | 
					    static properties = {
 | 
				
			||||||
        // isSessionActive: { type: Boolean, state: true },
 | 
					 | 
				
			||||||
        isTogglingSession: { type: Boolean, state: true },
 | 
					        isTogglingSession: { type: Boolean, state: true },
 | 
				
			||||||
        shortcuts: { type: Object, state: true },
 | 
					        shortcuts: { type: Object, state: true },
 | 
				
			||||||
        listenSessionStatus: { type: String, state: true },
 | 
					        listenSessionStatus: { type: String, state: true },
 | 
				
			||||||
@ -515,30 +514,12 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    invoke(channel, ...args) {
 | 
					 | 
				
			||||||
        if (this.wasJustDragged) return;
 | 
					 | 
				
			||||||
        if (window.api) {
 | 
					 | 
				
			||||||
            window.api.mainHeader.invoke(channel, ...args);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // return Promise.resolve();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    showSettingsWindow(element) {
 | 
					    showSettingsWindow(element) {
 | 
				
			||||||
        if (this.wasJustDragged) return;
 | 
					        if (this.wasJustDragged) return;
 | 
				
			||||||
        if (window.api) {
 | 
					        if (window.api) {
 | 
				
			||||||
            console.log(`[MainHeader] showSettingsWindow called at ${Date.now()}`);
 | 
					            console.log(`[MainHeader] showSettingsWindow called at ${Date.now()}`);
 | 
				
			||||||
            
 | 
					            window.api.mainHeader.showSettingsWindow();
 | 
				
			||||||
            window.api.mainHeader.cancelHideSettingsWindow();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (element) {
 | 
					 | 
				
			||||||
                const { left, top, width, height } = element.getBoundingClientRect();
 | 
					 | 
				
			||||||
                window.api.mainHeader.showSettingsWindow({
 | 
					 | 
				
			||||||
                    x: left,
 | 
					 | 
				
			||||||
                    y: top,
 | 
					 | 
				
			||||||
                    width,
 | 
					 | 
				
			||||||
                    height,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -559,9 +540,10 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
        this.isTogglingSession = true;
 | 
					        this.isTogglingSession = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const channel = 'listen:changeSession';
 | 
					 | 
				
			||||||
            const listenButtonText = this._getListenButtonText(this.listenSessionStatus);
 | 
					            const listenButtonText = this._getListenButtonText(this.listenSessionStatus);
 | 
				
			||||||
            await this.invoke(channel, listenButtonText);
 | 
					            if (window.api) {
 | 
				
			||||||
 | 
					                await window.api.mainHeader.sendListenButtonClick(listenButtonText);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        } catch (error) {
 | 
					        } catch (error) {
 | 
				
			||||||
            console.error('IPC invoke for session change failed:', error);
 | 
					            console.error('IPC invoke for session change failed:', error);
 | 
				
			||||||
            this.isTogglingSession = false;
 | 
					            this.isTogglingSession = false;
 | 
				
			||||||
@ -572,13 +554,26 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
        if (this.wasJustDragged) return;
 | 
					        if (this.wasJustDragged) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const channel = 'ask:toggleAskButton';
 | 
					            if (window.api) {
 | 
				
			||||||
            await this.invoke(channel);
 | 
					                await window.api.mainHeader.sendAskButtonClick();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        } catch (error) {
 | 
					        } catch (error) {
 | 
				
			||||||
            console.error('IPC invoke for ask button failed:', error);
 | 
					            console.error('IPC invoke for ask button failed:', error);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async _handleToggleAllWindowsVisibility() {
 | 
				
			||||||
 | 
					        if (this.wasJustDragged) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            if (window.api) {
 | 
				
			||||||
 | 
					                await window.api.mainHeader.sendToggleAllWindowsVisibility();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            console.error('IPC invoke for all windows visibility button failed:', error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    renderShortcut(accelerator) {
 | 
					    renderShortcut(accelerator) {
 | 
				
			||||||
        if (!accelerator) return html``;
 | 
					        if (!accelerator) return html``;
 | 
				
			||||||
@ -656,7 +651,7 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div class="header-actions" @click=${() => this.invoke('toggle-all-windows-visibility')}>
 | 
					                <div class="header-actions" @click=${() => this._handleToggleAllWindowsVisibility()}>
 | 
				
			||||||
                    <div class="action-text">
 | 
					                    <div class="action-text">
 | 
				
			||||||
                        <div class="action-text-content">Show/Hide</div>
 | 
					                        <div class="action-text-content">Show/Hide</div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -879,7 +879,7 @@ export class SettingsView extends LitElement {
 | 
				
			|||||||
    //////// after_modelStateService ////////
 | 
					    //////// after_modelStateService ////////
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    openShortcutEditor() {
 | 
					    openShortcutEditor() {
 | 
				
			||||||
        window.api.settingsView.openShortcutEditor();
 | 
					        window.api.settingsView.openShortcutSettingsWindow();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    connectedCallback() {
 | 
					    connectedCallback() {
 | 
				
			||||||
@ -1019,13 +1019,7 @@ export class SettingsView extends LitElement {
 | 
				
			|||||||
        window.api.settingsView.hideSettingsWindow();
 | 
					        window.api.settingsView.hideSettingsWindow();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // getMainShortcuts() {
 | 
					
 | 
				
			||||||
    //     return [
 | 
					 | 
				
			||||||
    //         { name: 'Show / Hide', key: '\\' },
 | 
					 | 
				
			||||||
    //         { name: 'Ask Anything', key: '↵' },
 | 
					 | 
				
			||||||
    //         { name: 'Scroll AI Response', key: '↕' }
 | 
					 | 
				
			||||||
    //     ];
 | 
					 | 
				
			||||||
    // }
 | 
					 | 
				
			||||||
    getMainShortcuts() {
 | 
					    getMainShortcuts() {
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
            { name: 'Show / Hide', accelerator: this.shortcuts.toggleVisibility },
 | 
					            { name: 'Show / Hide', accelerator: this.shortcuts.toggleVisibility },
 | 
				
			||||||
 | 
				
			|||||||
@ -179,7 +179,7 @@ export class ShortcutSettingsView extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    handleClose() {
 | 
					    handleClose() {
 | 
				
			||||||
        if (!window.api) return;
 | 
					        if (!window.api) return;
 | 
				
			||||||
        window.api.shortcutSettingsView.closeShortcutEditor();
 | 
					        window.api.shortcutSettingsView.closeShortcutSettingsWindow();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async handleResetToDefault() {
 | 
					    async handleResetToDefault() {
 | 
				
			||||||
 | 
				
			|||||||
@ -142,7 +142,14 @@ class WindowLayoutManager {
 | 
				
			|||||||
        const strategy = this.determineLayoutStrategy(headerBounds, screenWidth, screenHeight, relativeX, relativeY, workAreaX, workAreaY);
 | 
					        const strategy = this.determineLayoutStrategy(headerBounds, screenWidth, screenHeight, relativeX, relativeY, workAreaX, workAreaY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.positionFeatureWindows(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY);
 | 
					        this.positionFeatureWindows(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY);
 | 
				
			||||||
        this.positionSettingsWindow(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY);
 | 
					        const settings = this.windowPool.get('settings');
 | 
				
			||||||
 | 
					        if (settings && !settings.isDestroyed() && settings.isVisible()) {
 | 
				
			||||||
 | 
					            const settingPos = this.calculateSettingsWindowPosition();
 | 
				
			||||||
 | 
					            if (settingPos) {
 | 
				
			||||||
 | 
					                const { width, height } = settings.getBounds();
 | 
				
			||||||
 | 
					                settings.setBounds({ x: settingPos.x, y: settingPos.y, width, height });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -234,58 +241,54 @@ class WindowLayoutManager {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    positionSettingsWindow(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY) {
 | 
					    /**
 | 
				
			||||||
 | 
					     * @returns {{x: number, y: number} | null}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    calculateSettingsWindowPosition() {
 | 
				
			||||||
 | 
					        const header = this.windowPool.get('header');
 | 
				
			||||||
        const settings = this.windowPool.get('settings');
 | 
					        const settings = this.windowPool.get('settings');
 | 
				
			||||||
        if (!settings?.getBounds || !settings.isVisible()) return;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (settings.__lockedByButton) {
 | 
					        if (!header || header.isDestroyed() || !settings || settings.isDestroyed()) {
 | 
				
			||||||
            const headerDisplay = getCurrentDisplay(this.windowPool.get('header'));
 | 
					            return null;
 | 
				
			||||||
            const settingsDisplay = getCurrentDisplay(settings);
 | 
					 | 
				
			||||||
            if (headerDisplay.id !== settingsDisplay.id) {
 | 
					 | 
				
			||||||
                settings.__lockedByButton = false;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const headerBounds = header.getBounds();
 | 
				
			||||||
        const settingsBounds = settings.getBounds();
 | 
					        const settingsBounds = settings.getBounds();
 | 
				
			||||||
 | 
					        const display = getCurrentDisplay(header);
 | 
				
			||||||
 | 
					        const { x: workAreaX, y: workAreaY, width: screenWidth, height: screenHeight } = display.workArea;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const PAD = 5;
 | 
					        const PAD = 5;
 | 
				
			||||||
        const buttonPadding = 17;
 | 
					        const buttonPadding = 170;
 | 
				
			||||||
        let x = headerBounds.x + headerBounds.width - settingsBounds.width - buttonPadding;
 | 
					 | 
				
			||||||
        let y = headerBounds.y + headerBounds.height + PAD;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const otherVisibleWindows = [];
 | 
					        const x = headerBounds.x + headerBounds.width - settingsBounds.width + buttonPadding;
 | 
				
			||||||
        ['listen', 'ask'].forEach(name => {
 | 
					        const y = headerBounds.y + headerBounds.height + PAD;
 | 
				
			||||||
            const win = this.windowPool.get(name);
 | 
					 | 
				
			||||||
            if (win && win.isVisible() && !win.isDestroyed()) {
 | 
					 | 
				
			||||||
                otherVisibleWindows.push({ name, bounds: win.getBounds() });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const settingsNewBounds = { x, y, width: settingsBounds.width, height: settingsBounds.height };
 | 
					        const clampedX = Math.max(workAreaX + 10, Math.min(workAreaX + screenWidth - settingsBounds.width - 10, x));
 | 
				
			||||||
        let hasOverlap = otherVisibleWindows.some(otherWin => this.boundsOverlap(settingsNewBounds, otherWin.bounds));
 | 
					        const clampedY = Math.max(workAreaY + 10, Math.min(workAreaY + screenHeight - settingsBounds.height - 10, y));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (hasOverlap) {
 | 
					        return { x: Math.round(clampedX), y: Math.round(clampedY) };
 | 
				
			||||||
            x = headerBounds.x + headerBounds.width + PAD;
 | 
					    }
 | 
				
			||||||
            y = headerBounds.y;
 | 
					
 | 
				
			||||||
            if (x + settingsBounds.width > screenWidth - 10) {
 | 
					    positionShortcutSettingsWindow() {
 | 
				
			||||||
                x = headerBounds.x - settingsBounds.width - PAD;
 | 
					        const header = this.windowPool.get('header');
 | 
				
			||||||
            }
 | 
					        const shortcutSettings = this.windowPool.get('shortcut-settings');
 | 
				
			||||||
            if (x < 10) {
 | 
					
 | 
				
			||||||
                x = headerBounds.x + headerBounds.width - settingsBounds.width - buttonPadding;
 | 
					        if (!header || header.isDestroyed() || !shortcutSettings || shortcutSettings.isDestroyed()) {
 | 
				
			||||||
                y = headerBounds.y - settingsBounds.height - PAD;
 | 
					            return;
 | 
				
			||||||
                if (y < 10) {
 | 
					 | 
				
			||||||
                    x = headerBounds.x + headerBounds.width - settingsBounds.width;
 | 
					 | 
				
			||||||
                    y = headerBounds.y + headerBounds.height + PAD;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        x = Math.max(workAreaX + 10, Math.min(workAreaX + screenWidth - settingsBounds.width - 10, x));
 | 
					        const headerBounds = header.getBounds();
 | 
				
			||||||
        y = Math.max(workAreaY + 10, Math.min(workAreaY + screenHeight - settingsBounds.height - 10, y));
 | 
					        const shortcutBounds = shortcutSettings.getBounds();
 | 
				
			||||||
 | 
					        const display = getCurrentDisplay(header);
 | 
				
			||||||
 | 
					        const { workArea } = display;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        settings.setBounds({ x: Math.round(x), y: Math.round(y) });
 | 
					        let newX = Math.round(headerBounds.x + (headerBounds.width / 2) - (shortcutBounds.width / 2));
 | 
				
			||||||
        settings.moveTop();
 | 
					        let newY = Math.round(headerBounds.y);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        newX = Math.max(workArea.x, Math.min(newX, workArea.x + workArea.width - shortcutBounds.width));
 | 
				
			||||||
 | 
					        newY = Math.max(workArea.y, Math.min(newY, workArea.y + workArea.height - shortcutBounds.height));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        shortcutSettings.setBounds({ x: newX, y: newY, width: shortcutBounds.width, height: shortcutBounds.height });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,6 @@ const path = require('node:path');
 | 
				
			|||||||
const os = require('os');
 | 
					const os = require('os');
 | 
				
			||||||
const shortcutsService = require('../features/shortcuts/shortcutsService');
 | 
					const shortcutsService = require('../features/shortcuts/shortcutsService');
 | 
				
			||||||
const internalBridge = require('../bridge/internalBridge');
 | 
					const internalBridge = require('../bridge/internalBridge');
 | 
				
			||||||
const { EVENTS } = internalBridge;
 | 
					 | 
				
			||||||
const permissionRepository = require('../features/common/repositories/permission');
 | 
					const permissionRepository = require('../features/common/repositories/permission');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* ────────────────[ GLASS BYPASS ]─────────────── */
 | 
					/* ────────────────[ GLASS BYPASS ]─────────────── */
 | 
				
			||||||
@ -30,9 +29,6 @@ if (shouldUseLiquidGlass) {
 | 
				
			|||||||
/* ────────────────[ GLASS BYPASS ]─────────────── */
 | 
					/* ────────────────[ GLASS BYPASS ]─────────────── */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let isContentProtectionOn = true;
 | 
					let isContentProtectionOn = true;
 | 
				
			||||||
let currentDisplayId = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let mouseEventsIgnored = false;
 | 
					 | 
				
			||||||
let lastVisibleWindows = new Set(['header']);
 | 
					let lastVisibleWindows = new Set(['header']);
 | 
				
			||||||
const HEADER_HEIGHT = 47;
 | 
					const HEADER_HEIGHT = 47;
 | 
				
			||||||
const DEFAULT_WINDOW_WIDTH = 353;
 | 
					const DEFAULT_WINDOW_WIDTH = 353;
 | 
				
			||||||
@ -42,9 +38,7 @@ const windowPool = new Map();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
let settingsHideTimer = null;
 | 
					let settingsHideTimer = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let selectedCaptureSourceId = null;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// let shortcutEditorWindow = null;
 | 
					 | 
				
			||||||
let layoutManager = null;
 | 
					let layoutManager = null;
 | 
				
			||||||
function updateLayout() {
 | 
					function updateLayout() {
 | 
				
			||||||
    if (layoutManager) {
 | 
					    if (layoutManager) {
 | 
				
			||||||
@ -92,20 +86,69 @@ function fadeWindow(win, from, to, duration = FADE_DURATION, onComplete) {
 | 
				
			|||||||
  }, 1000 / FADE_FPS);
 | 
					  }, 1000 / FADE_FPS);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showSettingsWindow = () => {
 | 
				
			||||||
 | 
					    internalBridge.emit('window:requestVisibility', { name: 'settings', visible: true });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function setupAnimationController(windowPool, layoutManager, movementManager) {
 | 
					const hideSettingsWindow = () => {
 | 
				
			||||||
    internalBridge.on('request-window-visibility', ({ name, visible }) => {
 | 
					    internalBridge.emit('window:requestVisibility', { name: 'settings', visible: false });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cancelHideSettingsWindow = () => {
 | 
				
			||||||
 | 
					    internalBridge.emit('window:requestVisibility', { name: 'settings', visible: true });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function setupWindowController(windowPool, layoutManager, movementManager) {
 | 
				
			||||||
 | 
					    internalBridge.on('window:requestVisibility', ({ name, visible }) => {
 | 
				
			||||||
        handleWindowVisibilityRequest(windowPool, layoutManager, movementManager, name, visible);
 | 
					        handleWindowVisibilityRequest(windowPool, layoutManager, movementManager, name, visible);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    internalBridge.on('window:requestToggleAllWindowsVisibility', ({ targetVisibility }) => {
 | 
				
			||||||
 | 
					        changeAllWindowsVisibility(windowPool, targetVisibility);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function changeAllWindowsVisibility(windowPool, targetVisibility) {
 | 
				
			||||||
 | 
					    const header = windowPool.get('header');
 | 
				
			||||||
 | 
					    if (!header) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (typeof targetVisibility === 'boolean' &&
 | 
				
			||||||
 | 
					        header.isVisible() === targetVisibility) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					    if (header.isVisible()) {
 | 
				
			||||||
 | 
					      lastVisibleWindows.clear();
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					      windowPool.forEach((win, name) => {
 | 
				
			||||||
 | 
					        if (win && !win.isDestroyed() && win.isVisible()) {
 | 
				
			||||||
 | 
					          lastVisibleWindows.add(name);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					      lastVisibleWindows.forEach(name => {
 | 
				
			||||||
 | 
					        if (name === 'header') return;
 | 
				
			||||||
 | 
					        const win = windowPool.get(name);
 | 
				
			||||||
 | 
					        if (win && !win.isDestroyed()) win.hide();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      header.hide();
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					    lastVisibleWindows.forEach(name => {
 | 
				
			||||||
 | 
					      const win = windowPool.get(name);
 | 
				
			||||||
 | 
					      if (win && !win.isDestroyed())
 | 
				
			||||||
 | 
					        win.show();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 
 | 
					 * 
 | 
				
			||||||
 * @param {Map<string, BrowserWindow>} windowPool
 | 
					 * @param {Map<string, BrowserWindow>} windowPool
 | 
				
			||||||
 * @param {WindowLayoutManager} layoutManager 
 | 
					 * @param {WindowLayoutManager} layoutManager 
 | 
				
			||||||
 * @param {SmoothMovementManager} movementManager
 | 
					 * @param {SmoothMovementManager} movementManager
 | 
				
			||||||
 * @param {'listen' | 'ask'} name 
 | 
					 * @param {'listen' | 'ask' | 'settings' | 'shortcut-settings'} name 
 | 
				
			||||||
 * @param {boolean} shouldBeVisible 
 | 
					 * @param {boolean} shouldBeVisible 
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
async function handleWindowVisibilityRequest(windowPool, layoutManager, movementManager, name, shouldBeVisible) {
 | 
					async function handleWindowVisibilityRequest(windowPool, layoutManager, movementManager, name, shouldBeVisible) {
 | 
				
			||||||
@ -117,94 +160,171 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement
 | 
				
			|||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const isCurrentlyVisible = win.isVisible();
 | 
					    if (name !== 'settings') {
 | 
				
			||||||
    if (isCurrentlyVisible === shouldBeVisible) {
 | 
					        const isCurrentlyVisible = win.isVisible();
 | 
				
			||||||
        console.log(`[WindowManager] Window '${name}' is already in the desired state.`);
 | 
					        if (isCurrentlyVisible === shouldBeVisible) {
 | 
				
			||||||
 | 
					            console.log(`[WindowManager] Window '${name}' is already in the desired state.`);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const disableClicks = (selectedWindow) => {
 | 
				
			||||||
 | 
					        for (const [name, win] of windowPool) {
 | 
				
			||||||
 | 
					            if (win !== selectedWindow && !win.isDestroyed()) {
 | 
				
			||||||
 | 
					                win.setIgnoreMouseEvents(true, { forward: true });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const restoreClicks = () => {
 | 
				
			||||||
 | 
					        for (const [, win] of windowPool) {
 | 
				
			||||||
 | 
					            if (!win.isDestroyed()) win.setIgnoreMouseEvents(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (name === 'settings') {
 | 
				
			||||||
 | 
					        if (shouldBeVisible) {
 | 
				
			||||||
 | 
					            // Cancel any pending hide operations
 | 
				
			||||||
 | 
					            if (settingsHideTimer) {
 | 
				
			||||||
 | 
					                clearTimeout(settingsHideTimer);
 | 
				
			||||||
 | 
					                settingsHideTimer = null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const position = layoutManager.calculateSettingsWindowPosition();
 | 
				
			||||||
 | 
					            if (position) {
 | 
				
			||||||
 | 
					                win.setBounds(position);
 | 
				
			||||||
 | 
					                win.__lockedByButton = true;
 | 
				
			||||||
 | 
					                win.show();
 | 
				
			||||||
 | 
					                win.moveTop();
 | 
				
			||||||
 | 
					                win.setAlwaysOnTop(true);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.warn('[WindowManager] Could not calculate settings window position.');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Hide after a delay
 | 
				
			||||||
 | 
					            if (settingsHideTimer) {
 | 
				
			||||||
 | 
					                clearTimeout(settingsHideTimer);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            settingsHideTimer = setTimeout(() => {
 | 
				
			||||||
 | 
					                if (win && !win.isDestroyed()) {
 | 
				
			||||||
 | 
					                    win.setAlwaysOnTop(false);
 | 
				
			||||||
 | 
					                    win.hide();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                settingsHideTimer = null;
 | 
				
			||||||
 | 
					            }, 200);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            win.__lockedByButton = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const otherName = name === 'listen' ? 'ask' : 'listen';
 | 
					 | 
				
			||||||
    const otherWin = windowPool.get(otherName);
 | 
					 | 
				
			||||||
    const isOtherWinVisible = otherWin && !otherWin.isDestroyed() && otherWin.isVisible();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const ANIM_OFFSET_X = 100; 
 | 
					 | 
				
			||||||
    const ANIM_OFFSET_Y = 20; 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (shouldBeVisible) {
 | 
					 | 
				
			||||||
        win.setOpacity(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (name === 'listen') {
 | 
					 | 
				
			||||||
            if (!isOtherWinVisible) {
 | 
					 | 
				
			||||||
                const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: false });
 | 
					 | 
				
			||||||
                if (!targets.listen) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const startPos = { x: targets.listen.x - ANIM_OFFSET_X, y: targets.listen.y };
 | 
					 | 
				
			||||||
                win.setBounds(startPos);
 | 
					 | 
				
			||||||
                win.show();
 | 
					 | 
				
			||||||
                fadeWindow(win, 0, 1);
 | 
					 | 
				
			||||||
                movementManager.animateWindow(win, targets.listen.x, targets.listen.y);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (name === 'shortcut-settings') {
 | 
				
			||||||
 | 
					        if (shouldBeVisible) {
 | 
				
			||||||
 | 
					            layoutManager.positionShortcutSettingsWindow();
 | 
				
			||||||
 | 
					            if (process.platform === 'darwin') {
 | 
				
			||||||
 | 
					                win.setAlwaysOnTop(true, 'screen-saver');
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: true });
 | 
					                win.setAlwaysOnTop(true);
 | 
				
			||||||
                if (!targets.listen || !targets.ask) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const startListenPos = { x: targets.listen.x - ANIM_OFFSET_X, y: targets.listen.y };
 | 
					 | 
				
			||||||
                win.setBounds(startListenPos);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                win.show();
 | 
					 | 
				
			||||||
                fadeWindow(win, 0, 1);
 | 
					 | 
				
			||||||
                movementManager.animateWindow(otherWin, targets.ask.x, targets.ask.y);
 | 
					 | 
				
			||||||
                movementManager.animateWindow(win, targets.listen.x, targets.listen.y);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else if (name === 'ask') {
 | 
					            // globalShortcut.unregisterAll();
 | 
				
			||||||
            if (!isOtherWinVisible) {
 | 
					            disableClicks(win);
 | 
				
			||||||
                const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: false, ask: true });
 | 
					            win.show();
 | 
				
			||||||
                if (!targets.ask) return;
 | 
					        } else {
 | 
				
			||||||
 | 
					            if (process.platform === 'darwin') {
 | 
				
			||||||
                const startPos = { x: targets.ask.x, y: targets.ask.y - ANIM_OFFSET_Y };
 | 
					                win.setAlwaysOnTop(false, 'screen-saver');
 | 
				
			||||||
                win.setBounds(startPos);
 | 
					 | 
				
			||||||
                win.show();
 | 
					 | 
				
			||||||
                fadeWindow(win, 0, 1);
 | 
					 | 
				
			||||||
                movementManager.animateWindow(win, targets.ask.x, targets.ask.y);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: true });
 | 
					                win.setAlwaysOnTop(false);
 | 
				
			||||||
                if (!targets.listen || !targets.ask) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const startAskPos = { x: targets.ask.x, y: targets.ask.y - ANIM_OFFSET_Y };
 | 
					 | 
				
			||||||
                win.setBounds(startAskPos);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                win.show();
 | 
					 | 
				
			||||||
                fadeWindow(win, 0, 1);
 | 
					 | 
				
			||||||
                movementManager.animateWindow(otherWin, targets.listen.x, targets.listen.y);
 | 
					 | 
				
			||||||
                movementManager.animateWindow(win, targets.ask.x, targets.ask.y);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            restoreClicks();
 | 
				
			||||||
 | 
					            win.hide();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else {
 | 
					        return;
 | 
				
			||||||
        const currentBounds = win.getBounds();
 | 
					    }
 | 
				
			||||||
        fadeWindow(
 | 
					 | 
				
			||||||
            win, 1, 0, FADE_DURATION,
 | 
					 | 
				
			||||||
            () => win.hide()
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        if (name === 'listen') {
 | 
					 | 
				
			||||||
            if (!isOtherWinVisible) {
 | 
					 | 
				
			||||||
                const targetX = currentBounds.x - ANIM_OFFSET_X;
 | 
					 | 
				
			||||||
                movementManager.animateWindow(win, targetX, currentBounds.y);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                const targetX = currentBounds.x - currentBounds.width;
 | 
					 | 
				
			||||||
                movementManager.animateWindow(win, targetX, currentBounds.y);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else if (name === 'ask') {
 | 
					 | 
				
			||||||
            if (!isOtherWinVisible) {
 | 
					 | 
				
			||||||
                 const targetY = currentBounds.y - ANIM_OFFSET_Y;
 | 
					 | 
				
			||||||
                 movementManager.animateWindow(win, currentBounds.x, targetY);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                const targetAskY = currentBounds.y - ANIM_OFFSET_Y;
 | 
					 | 
				
			||||||
                movementManager.animateWindow(win, currentBounds.x, targetAskY);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: false });
 | 
					    if (name === 'listen' || name === 'ask') {
 | 
				
			||||||
                if (targets.listen) {
 | 
					        const otherName = name === 'listen' ? 'ask' : 'listen';
 | 
				
			||||||
 | 
					        const otherWin = windowPool.get(otherName);
 | 
				
			||||||
 | 
					        const isOtherWinVisible = otherWin && !otherWin.isDestroyed() && otherWin.isVisible();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const ANIM_OFFSET_X = 100; 
 | 
				
			||||||
 | 
					        const ANIM_OFFSET_Y = 20; 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (shouldBeVisible) {
 | 
				
			||||||
 | 
					            win.setOpacity(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (name === 'listen') {
 | 
				
			||||||
 | 
					                if (!isOtherWinVisible) {
 | 
				
			||||||
 | 
					                    const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: false });
 | 
				
			||||||
 | 
					                    if (!targets.listen) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const startPos = { x: targets.listen.x - ANIM_OFFSET_X, y: targets.listen.y };
 | 
				
			||||||
 | 
					                    win.setBounds(startPos);
 | 
				
			||||||
 | 
					                    win.show();
 | 
				
			||||||
 | 
					                    fadeWindow(win, 0, 1);
 | 
				
			||||||
 | 
					                    movementManager.animateWindow(win, targets.listen.x, targets.listen.y);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: true });
 | 
				
			||||||
 | 
					                    if (!targets.listen || !targets.ask) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const startListenPos = { x: targets.listen.x - ANIM_OFFSET_X, y: targets.listen.y };
 | 
				
			||||||
 | 
					                    win.setBounds(startListenPos);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    win.show();
 | 
				
			||||||
 | 
					                    fadeWindow(win, 0, 1);
 | 
				
			||||||
 | 
					                    movementManager.animateWindow(otherWin, targets.ask.x, targets.ask.y);
 | 
				
			||||||
 | 
					                    movementManager.animateWindow(win, targets.listen.x, targets.listen.y);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else if (name === 'ask') {
 | 
				
			||||||
 | 
					                if (!isOtherWinVisible) {
 | 
				
			||||||
 | 
					                    const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: false, ask: true });
 | 
				
			||||||
 | 
					                    if (!targets.ask) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const startPos = { x: targets.ask.x, y: targets.ask.y - ANIM_OFFSET_Y };
 | 
				
			||||||
 | 
					                    win.setBounds(startPos);
 | 
				
			||||||
 | 
					                    win.show();
 | 
				
			||||||
 | 
					                    fadeWindow(win, 0, 1);
 | 
				
			||||||
 | 
					                    movementManager.animateWindow(win, targets.ask.x, targets.ask.y);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: true });
 | 
				
			||||||
 | 
					                    if (!targets.listen || !targets.ask) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const startAskPos = { x: targets.ask.x, y: targets.ask.y - ANIM_OFFSET_Y };
 | 
				
			||||||
 | 
					                    win.setBounds(startAskPos);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    win.show();
 | 
				
			||||||
 | 
					                    fadeWindow(win, 0, 1);
 | 
				
			||||||
                    movementManager.animateWindow(otherWin, targets.listen.x, targets.listen.y);
 | 
					                    movementManager.animateWindow(otherWin, targets.listen.x, targets.listen.y);
 | 
				
			||||||
 | 
					                    movementManager.animateWindow(win, targets.ask.x, targets.ask.y);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const currentBounds = win.getBounds();
 | 
				
			||||||
 | 
					            fadeWindow(
 | 
				
			||||||
 | 
					                win, 1, 0, FADE_DURATION,
 | 
				
			||||||
 | 
					                () => win.hide()
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            if (name === 'listen') {
 | 
				
			||||||
 | 
					                if (!isOtherWinVisible) {
 | 
				
			||||||
 | 
					                    const targetX = currentBounds.x - ANIM_OFFSET_X;
 | 
				
			||||||
 | 
					                    movementManager.animateWindow(win, targetX, currentBounds.y);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    const targetX = currentBounds.x - currentBounds.width;
 | 
				
			||||||
 | 
					                    movementManager.animateWindow(win, targetX, currentBounds.y);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else if (name === 'ask') {
 | 
				
			||||||
 | 
					                if (!isOtherWinVisible) {
 | 
				
			||||||
 | 
					                    const targetY = currentBounds.y - ANIM_OFFSET_Y;
 | 
				
			||||||
 | 
					                    movementManager.animateWindow(win, currentBounds.x, targetY);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    const targetAskY = currentBounds.y - ANIM_OFFSET_Y;
 | 
				
			||||||
 | 
					                    movementManager.animateWindow(win, currentBounds.x, targetAskY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: false });
 | 
				
			||||||
 | 
					                    if (targets.listen) {
 | 
				
			||||||
 | 
					                        movementManager.animateWindow(otherWin, targets.listen.x, targets.listen.y);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -276,62 +396,6 @@ const resizeHeaderWindow = ({ width, height }) => {
 | 
				
			|||||||
    return { success: false, error: 'Header window not found' };
 | 
					    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 openLoginPage = () => {
 | 
				
			||||||
    const webUrl = process.env.pickleglass_WEB_URL || 'http://localhost:3000';
 | 
					    const webUrl = process.env.pickleglass_WEB_URL || 'http://localhost:3000';
 | 
				
			||||||
@ -474,7 +538,7 @@ function createFeatureWindows(header, namesToCreate) {
 | 
				
			|||||||
            case 'shortcut-settings': {
 | 
					            case 'shortcut-settings': {
 | 
				
			||||||
                const shortcutEditor = new BrowserWindow({
 | 
					                const shortcutEditor = new BrowserWindow({
 | 
				
			||||||
                    ...commonChildOptions,
 | 
					                    ...commonChildOptions,
 | 
				
			||||||
                    width: 420,
 | 
					                    width: 353,
 | 
				
			||||||
                    height: 720,
 | 
					                    height: 720,
 | 
				
			||||||
                    modal: false,
 | 
					                    modal: false,
 | 
				
			||||||
                    parent: undefined,
 | 
					                    parent: undefined,
 | 
				
			||||||
@ -482,36 +546,11 @@ function createFeatureWindows(header, namesToCreate) {
 | 
				
			|||||||
                    titleBarOverlay: false,
 | 
					                    titleBarOverlay: false,
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                shortcutEditor.setContentProtection(isContentProtectionOn);
 | 
				
			||||||
 | 
					                shortcutEditor.setVisibleOnAllWorkspaces(true,{visibleOnFullScreen:true});
 | 
				
			||||||
                if (process.platform === 'darwin') {
 | 
					                if (process.platform === 'darwin') {
 | 
				
			||||||
                    shortcutEditor.setAlwaysOnTop(true, 'screen-saver');
 | 
					                    shortcutEditor.setWindowButtonVisibility(false);
 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    shortcutEditor.setAlwaysOnTop(true);
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            
 | 
					 | 
				
			||||||
                /* ──────────[ ① 다른 창 클릭 차단 ]────────── */
 | 
					 | 
				
			||||||
                const disableClicks = () => {
 | 
					 | 
				
			||||||
                    for (const [name, win] of windowPool) {
 | 
					 | 
				
			||||||
                        if (win !== shortcutEditor && !win.isDestroyed()) {
 | 
					 | 
				
			||||||
                            win.setIgnoreMouseEvents(true, { forward: true });
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
                const restoreClicks = () => {
 | 
					 | 
				
			||||||
                    for (const [, win] of windowPool) {
 | 
					 | 
				
			||||||
                        if (!win.isDestroyed()) win.setIgnoreMouseEvents(false);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const header = windowPool.get('header');
 | 
					 | 
				
			||||||
                if (header && !header.isDestroyed()) {
 | 
					 | 
				
			||||||
                    const { x, y, width } = header.getBounds();
 | 
					 | 
				
			||||||
                    shortcutEditor.setBounds({ x, y, width });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                shortcutEditor.once('ready-to-show', () => {
 | 
					 | 
				
			||||||
                    disableClicks(); 
 | 
					 | 
				
			||||||
                    shortcutEditor.show();
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const loadOptions = { query: { view: 'shortcut-settings' } };
 | 
					                const loadOptions = { query: { view: 'shortcut-settings' } };
 | 
				
			||||||
                if (!shouldUseLiquidGlass) {
 | 
					                if (!shouldUseLiquidGlass) {
 | 
				
			||||||
@ -526,23 +565,11 @@ function createFeatureWindows(header, namesToCreate) {
 | 
				
			|||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                shortcutEditor.on('closed', () => {
 | 
					 | 
				
			||||||
                    restoreClicks();
 | 
					 | 
				
			||||||
                    windowPool.delete('shortcut-settings');
 | 
					 | 
				
			||||||
                    console.log('[Shortcuts] Re-enabled after editing.');
 | 
					 | 
				
			||||||
                    shortcutsService.registerShortcuts();
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                shortcutEditor.webContents.once('dom-ready', async () => {
 | 
					 | 
				
			||||||
                    const keybinds = await shortcutsService.loadKeybinds();
 | 
					 | 
				
			||||||
                    shortcutEditor.webContents.send('load-shortcuts', keybinds);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                windowPool.set('shortcut-settings', shortcutEditor);
 | 
				
			||||||
                if (!app.isPackaged) {
 | 
					                if (!app.isPackaged) {
 | 
				
			||||||
                    shortcutEditor.webContents.openDevTools({ mode: 'detach' });
 | 
					                    shortcutEditor.webContents.openDevTools({ mode: 'detach' });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                windowPool.set('shortcut-settings', shortcutEditor);
 | 
					 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -556,6 +583,7 @@ function createFeatureWindows(header, namesToCreate) {
 | 
				
			|||||||
        createFeatureWindow('listen');
 | 
					        createFeatureWindow('listen');
 | 
				
			||||||
        createFeatureWindow('ask');
 | 
					        createFeatureWindow('ask');
 | 
				
			||||||
        createFeatureWindow('settings');
 | 
					        createFeatureWindow('settings');
 | 
				
			||||||
 | 
					        createFeatureWindow('shortcut-settings');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -593,35 +621,7 @@ function getDisplayById(displayId) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function toggleAllWindowsVisibility() {
 | 
					
 | 
				
			||||||
    const header = windowPool.get('header');
 | 
					 | 
				
			||||||
    if (!header) return;
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
    if (header.isVisible()) {
 | 
					 | 
				
			||||||
      lastVisibleWindows.clear();
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
      windowPool.forEach((win, name) => {
 | 
					 | 
				
			||||||
        if (win && !win.isDestroyed() && win.isVisible()) {
 | 
					 | 
				
			||||||
          lastVisibleWindows.add(name);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
      lastVisibleWindows.forEach(name => {
 | 
					 | 
				
			||||||
        if (name === 'header') return;
 | 
					 | 
				
			||||||
        const win = windowPool.get(name);
 | 
					 | 
				
			||||||
        if (win && !win.isDestroyed()) win.hide();
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      header.hide();
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
    lastVisibleWindows.forEach(name => {
 | 
					 | 
				
			||||||
      const win = windowPool.get(name);
 | 
					 | 
				
			||||||
      if (win && !win.isDestroyed())
 | 
					 | 
				
			||||||
        win.show();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function createWindows() {
 | 
					function createWindows() {
 | 
				
			||||||
@ -690,7 +690,7 @@ function createWindows() {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setupIpcHandlers(movementManager);
 | 
					    setupIpcHandlers(movementManager);
 | 
				
			||||||
    setupAnimationController(windowPool, layoutManager, movementManager);
 | 
					    setupWindowController(windowPool, layoutManager, movementManager);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (currentHeaderState === 'main') {
 | 
					    if (currentHeaderState === 'main') {
 | 
				
			||||||
        createFeatureWindows(header, ['listen', 'ask', 'settings', 'shortcut-settings']);
 | 
					        createFeatureWindows(header, ['listen', 'ask', 'settings', 'shortcut-settings']);
 | 
				
			||||||
@ -850,13 +850,6 @@ const adjustWindowHeight = (sender, targetHeight) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const closeWindow = (windowName) => {
 | 
					 | 
				
			||||||
    const win = windowPool.get(windowName);
 | 
					 | 
				
			||||||
    if (win && !win.isDestroyed()) {
 | 
					 | 
				
			||||||
        win.close();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    updateLayout,
 | 
					    updateLayout,
 | 
				
			||||||
    createWindows,
 | 
					    createWindows,
 | 
				
			||||||
@ -864,14 +857,11 @@ module.exports = {
 | 
				
			|||||||
    toggleContentProtection,
 | 
					    toggleContentProtection,
 | 
				
			||||||
    resizeHeaderWindow,
 | 
					    resizeHeaderWindow,
 | 
				
			||||||
    getContentProtectionStatus,
 | 
					    getContentProtectionStatus,
 | 
				
			||||||
    openShortcutEditor,
 | 
					 | 
				
			||||||
    showSettingsWindow,
 | 
					    showSettingsWindow,
 | 
				
			||||||
    hideSettingsWindow,
 | 
					    hideSettingsWindow,
 | 
				
			||||||
    cancelHideSettingsWindow,
 | 
					    cancelHideSettingsWindow,
 | 
				
			||||||
    openLoginPage,
 | 
					    openLoginPage,
 | 
				
			||||||
    moveWindowStep,
 | 
					    moveWindowStep,
 | 
				
			||||||
    closeWindow,
 | 
					 | 
				
			||||||
    toggleAllWindowsVisibility,
 | 
					 | 
				
			||||||
    handleHeaderStateChanged,
 | 
					    handleHeaderStateChanged,
 | 
				
			||||||
    handleHeaderAnimationFinished,
 | 
					    handleHeaderAnimationFinished,
 | 
				
			||||||
    getHeaderPosition,
 | 
					    getHeaderPosition,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user