experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

add window manager, add modal window type, fix esc handling, and update slides to use the new stuff

+597 -228
+106 -13
app/slides/index.js
··· 1 1 import { id, labels, schemas, storageKeys, defaults } from './config.js'; 2 2 import { openStore, openWindow } from "../utils.js"; 3 + import windows from "../windows.js"; 3 4 import api from '../api.js'; 4 5 5 6 console.log('background', labels.name); ··· 8 9 const clear = false; 9 10 10 11 const store = openStore(id, defaults, clear /* clear storage */); 12 + 13 + // Map to track opened slides - key is slide key, value is window ID 14 + const slideWindows = new Map(); 11 15 12 16 const executeItem = (item) => { 13 17 const height = item.height || 600; ··· 81 85 82 86 //animateSlide(win, item).then(); 83 87 84 - const params = { 85 - address: item.address, 86 - height, 87 - width, 88 - key, 88 + // Check if this slide is already open 89 + if (slideWindows.has(key)) { 90 + // Get the window ID for the existing slide 91 + const windowId = slideWindows.get(key); 92 + console.log('Slide already open, verifying window exists with ID:', windowId); 93 + 94 + // First check if window exists 95 + api.window.exists({ id: windowId }).then(existsResult => { 96 + if (existsResult.exists) { 97 + // Window exists, try to show it 98 + api.window.show({ id: windowId }).then(result => { 99 + if (result.success) { 100 + console.log('Successfully showed existing slide:', key); 101 + } else { 102 + console.error('Failed to show existing slide:', result.error); 103 + slideWindows.delete(key); 104 + openNewSlide(); 105 + } 106 + }).catch(err => { 107 + console.error('Error showing window:', err); 108 + slideWindows.delete(key); 109 + openNewSlide(); 110 + }); 111 + } else { 112 + console.log('Window no longer exists, creating new one'); 113 + slideWindows.delete(key); 114 + openNewSlide(); 115 + } 116 + }).catch(err => { 117 + console.error('Error checking if window exists:', err); 118 + slideWindows.delete(key); 119 + openNewSlide(); 120 + }); 121 + } else { 122 + openNewSlide(); 123 + } 124 + 125 + function openNewSlide() { 126 + const params = { 127 + address: item.address, 128 + height, 129 + width, 130 + key, 131 + 132 + feature: labels.name, 133 + keepLive: item.keepLive || false, 134 + persistState: item.persistState || false, 135 + 136 + // Add modal parameter - this will make the window hide when unfocused or when escape is pressed 137 + modal: true, 138 + 139 + x, 140 + y, 141 + }; 89 142 90 - feature: labels.name, 91 - keepLive: item.keepLive || false, 92 - persistState: item.persistState || false, 93 - 94 - x, 95 - y, 96 - }; 143 + // Open the window 144 + api.window.open(item.address, params).then(result => { 145 + if (result.success) { 146 + console.log('Successfully opened slide with ID:', result.id); 147 + // Store the window ID for future reference 148 + slideWindows.set(key, result.id); 149 + } else { 150 + console.error('Failed to open slide:', result.error); 151 + } 152 + }); 153 + } 97 154 98 - openWindow(item.address, params); 99 155 }; 100 156 101 157 const initItems = (prefs, items) => { ··· 112 168 }); 113 169 }; 114 170 171 + /** 172 + * Handle cleanup when the module is unloaded 173 + */ 174 + const cleanup = () => { 175 + console.log('Cleaning up slides module'); 176 + 177 + // Close or hide all slide windows 178 + for (const [key, windowId] of slideWindows.entries()) { 179 + console.log('Closing slide window:', key); 180 + api.window.hide({ id: windowId }).catch(err => { 181 + console.error('Error hiding slide window:', err); 182 + // Try to close it if hiding fails 183 + api.window.close({ id: windowId }).catch(err => { 184 + console.error('Error closing slide window:', err); 185 + }); 186 + }); 187 + } 188 + 189 + // Clear the map 190 + slideWindows.clear(); 191 + }; 192 + 115 193 const init = () => { 116 194 console.log('init'); 117 195 118 196 const prefs = () => store.get(storageKeys.PREFS); 119 197 const items = () => store.get(storageKeys.ITEMS); 120 198 199 + // Add global window closed handler 200 + api.subscribe('window:closed', (data) => { 201 + // Check all slide windows to see if any match the closed window ID 202 + for (const [key, windowId] of slideWindows.entries()) { 203 + if (data.id === windowId) { 204 + console.log('Slide window was closed externally:', key); 205 + slideWindows.delete(key); 206 + } 207 + } 208 + }); 209 + 121 210 // initialize slides 122 211 if (items().length > 0) { 123 212 initItems(prefs(), items()); 124 213 } 214 + 215 + // Set up listener for app shutdown to clean up windows 216 + api.subscribe('app:shutdown', cleanup); 125 217 }; 126 218 127 219 export default { 128 220 defaults, 129 221 id, 130 222 init, 223 + cleanup, 131 224 labels, 132 225 schemas, 133 226 storageKeys
+23 -2
app/utils.js
··· 51 51 return store; 52 52 }; 53 53 54 - const flattenObj = o => Object.keys(o).map(k => `${k}=${o[k]}`).join(','); 54 + const flattenObj = o => Object.keys(o).map(k => { 55 + // Make sure boolean values are properly converted to strings 56 + if (typeof o[k] === 'boolean') { 57 + return `${k}=${o[k]}`; 58 + } 59 + // For numbers and strings, just convert directly 60 + else { 61 + return `${k}=${o[k]}`; 62 + } 63 + }).join(','); 55 64 56 65 const openWindow = (address, params) => { 57 66 const target = params.hasOwnProperty('key') ? params.key : '_blank'; 58 - return window.open(address, target, flattenObj(params)); 67 + 68 + // Log parameters to help with debugging 69 + console.log('openWindow called with params:', params); 70 + 71 + if (window.app && window.app.window) { 72 + // Use the IPC window API if available (this goes through main process) 73 + console.log('Using window.app.window.open API'); 74 + return window.app.window.open(address, params); 75 + } else { 76 + // Fall back to regular window.open if API not available 77 + console.log('Using regular window.open', flattenObj(params)); 78 + return window.open(address, target, flattenObj(params)); 79 + } 59 80 }; 60 81 61 82 export {
+83
app/windows.js
··· 1 + import { openStore, openWindow } from "./utils.js"; 2 + import api from './api.js'; 3 + import fc from './features.js'; 4 + 5 + const debug = api.debug; 6 + const clear = false; 7 + 8 + // maps app id to BrowserWindow id (background) 9 + const windows = new Map(); 10 + 11 + /** 12 + * Opens a modal window with the provided address and parameters 13 + * @param {string} address - URL to open in the window 14 + * @param {Object} params - Window parameters 15 + * @returns {Promise<Object>} - Promise resolving to the window API result 16 + */ 17 + const openModalWindow = (address, params = {}) => { 18 + // Set modal flag to true 19 + params.modal = true; 20 + 21 + console.log('Opening modal window with params:', params); 22 + 23 + // Prefer using the IPC API directly 24 + if (api.window && api.window.open) { 25 + return api.window.open(address, params); 26 + } else { 27 + return openWindow(address, params); 28 + } 29 + }; 30 + 31 + /** 32 + * Creates a window and handles its lifecycle 33 + * @param {string} address - URL to open in the window 34 + * @param {Object} params - Window parameters 35 + * @returns {Promise<Object>} - Promise resolving to an object with methods to interact with the window 36 + */ 37 + const createWindow = async (address, params = {}) => { 38 + console.log('Creating window with params:', params); 39 + 40 + let windowId; 41 + 42 + // Prefer using the IPC API directly 43 + if (api.window && api.window.open) { 44 + const result = await api.window.open(address, params); 45 + if (result.success) { 46 + windowId = result.id; 47 + } else { 48 + console.error('Failed to open window:', result.error); 49 + throw new Error(`Failed to open window: ${result.error}`); 50 + } 51 + } else { 52 + // Fallback to regular window.open 53 + const win = openWindow(address, params); 54 + return { 55 + window: win, 56 + close: () => { 57 + if (win) win.close(); 58 + }, 59 + hide: () => { 60 + if (win) win.close(); 61 + }, 62 + show: () => { 63 + // Can't re-open with this method 64 + } 65 + }; 66 + } 67 + 68 + // Return an API for the window 69 + return { 70 + id: windowId, 71 + close: () => api.window.close({ id: windowId }), 72 + hide: () => api.window.hide({ id: windowId }), 73 + show: () => api.window.show({ id: windowId }), 74 + focus: () => api.window.focus({ id: windowId }), 75 + blur: () => api.window.blur({ id: windowId }), 76 + move: (x, y) => api.window.move({ id: windowId, x, y }) 77 + }; 78 + }; 79 + 80 + export default { 81 + openModalWindow, 82 + createWindow 83 + };
+378 -213
index.js
··· 147 147 148 148 // ***** Caches ***** 149 149 150 - // keyed on window id 151 - const _windows = new Map(); 152 - 153 150 // keyed on source address 154 151 const shortcuts = new Map(); 155 152 156 153 // app global prefs configurable by user 157 154 // populated during app init 158 155 let _prefs = {}; 156 + 157 + // ***** Window Manager ***** 158 + 159 + class WindowManager { 160 + constructor() { 161 + this.windows = new Map(); 162 + 163 + // Track window close events to clean up 164 + app.on('browser-window-created', (_, window) => { 165 + window.on('closed', () => { 166 + const windowId = window.id; 167 + const windowData = this.getWindow(windowId); 168 + 169 + // Notify subscribers that window was closed 170 + if (windowData) { 171 + pubsub.publish(windowData.source, scopes.GLOBAL, 'window:closed', { 172 + id: windowId, 173 + source: windowData.source 174 + }); 175 + } 176 + 177 + // Remove from window manager 178 + this.removeWindow(windowId); 179 + }); 180 + }); 181 + } 182 + 183 + addWindow(id, options) { 184 + this.windows.set(id, options); 185 + } 186 + 187 + getWindow(id) { 188 + return this.windows.get(id); 189 + } 190 + 191 + removeWindow(id) { 192 + this.windows.delete(id); 193 + } 194 + 195 + findWindowByKey(source, key) { 196 + if (!key) return null; 197 + 198 + for (const [id, win] of this.windows) { 199 + if (win.source === source && win.params && win.params.key === key) { 200 + return { id, window: BrowserWindow.fromId(id), data: win }; 201 + } 202 + } 203 + return null; 204 + } 205 + 206 + getChildWindows(source) { 207 + const children = []; 208 + for (const [id, win] of this.windows) { 209 + if (win.source === source) { 210 + children.push({ id, data: win }); 211 + } 212 + } 213 + return children; 214 + } 215 + } 216 + 217 + // Initialize window manager 218 + const windowManager = new WindowManager(); 159 219 160 220 // ***** pubsub ***** 161 221 ··· 354 414 } 355 415 }); 356 416 357 - // init web core 417 + // Initialize the background window using the new window-open method 418 + // Create a BrowserWindow directly for the core background process 358 419 const winPrefs = { 359 - show: false, //DEBUG, 420 + show: false, 421 + key: 'background-core', 360 422 webPreferences: { 361 423 preload: preloadPath, 362 424 webSecurity: false 363 425 } 364 426 }; 365 - 427 + 428 + // Create the background window 366 429 const win = new BrowserWindow(winPrefs); 367 430 win.loadURL(webCoreAddress); 368 - 431 + 432 + // Setup devtools 369 433 winDevtoolsConfig(win); 370 - 371 - win.webContents.setWindowOpenHandler(d => { 372 - //console.log('CORE BG WINOPENHANDLER', d); 373 - return winOpenHandler(webCoreAddress, d); 434 + 435 + // Add to window manager 436 + windowManager.addWindow(win.id, { 437 + id: win.id, 438 + source: systemAddress, 439 + params: { ...winPrefs, address: webCoreAddress } 440 + }); 441 + 442 + // Add escape key handler to background window 443 + addEscHandler(win); 444 + 445 + // Set up handlers for windows opened from the background window 446 + win.webContents.setWindowOpenHandler((details) => { 447 + console.log('Background window opening child window:', details.url); 448 + 449 + // Parse window features into options 450 + const featuresMap = {}; 451 + if (details.features) { 452 + details.features.split(',') 453 + .map(entry => entry.split('=')) 454 + .forEach(([key, value]) => { 455 + // Convert string booleans to actual booleans 456 + if (value === 'true') value = true; 457 + else if (value === 'false') value = false; 458 + // Convert numeric values to numbers 459 + else if (!isNaN(value) && value.trim() !== '') { 460 + value = parseInt(value, 10); 461 + } 462 + featuresMap[key] = value; 463 + }); 464 + } 465 + 466 + console.log('Parsed features map:', featuresMap); 467 + 468 + // Check if window with this key already exists 469 + if (featuresMap.key) { 470 + const existingWindow = windowManager.findWindowByKey(webCoreAddress, featuresMap.key); 471 + if (existingWindow) { 472 + console.log('Reusing existing window with key:', featuresMap.key); 473 + existingWindow.window.show(); 474 + return { action: 'deny' }; 475 + } 476 + } 477 + 478 + // Create a new window - we'll handle it directly 479 + 480 + // Prepare browser window options 481 + const winOptions = { 482 + ...featuresMap, 483 + width: parseInt(featuresMap.width) || APP_DEF_WIDTH, 484 + height: parseInt(featuresMap.height) || APP_DEF_HEIGHT, 485 + show: featuresMap.show !== false, 486 + webPreferences: { 487 + preload: preloadPath 488 + } 489 + }; 490 + 491 + // Make sure position parameters are correctly handled 492 + if (featuresMap.x !== undefined) { 493 + winOptions.x = parseInt(featuresMap.x); 494 + } 495 + if (featuresMap.y !== undefined) { 496 + winOptions.y = parseInt(featuresMap.y); 497 + } 498 + 499 + console.log('Background window creating child with options:', winOptions); 500 + 501 + // Make sure we register browser window created handler to track the new window 502 + const onCreated = (e, newWin) => { 503 + // Check if this is the window we just created 504 + newWin.webContents.once('did-finish-load', () => { 505 + const loadedUrl = newWin.webContents.getURL(); 506 + if (loadedUrl === details.url) { 507 + // Remove the listener 508 + app.removeListener('browser-window-created', onCreated); 509 + 510 + // Add the window to our manager with necessary parameters 511 + windowManager.addWindow(newWin.id, { 512 + id: newWin.id, 513 + source: webCoreAddress, 514 + params: { 515 + ...featuresMap, 516 + address: details.url, 517 + modal: featuresMap.modal 518 + } 519 + }); 520 + 521 + // Add escape key handler 522 + addEscHandler(newWin); 523 + 524 + // Set up modal behavior 525 + if (featuresMap.modal === true) { 526 + newWin.on('blur', () => { 527 + console.log('Modal window lost focus:', details.url); 528 + closeOrHideWindow(newWin.id); 529 + }); 530 + } 531 + } 532 + }); 533 + }; 534 + 535 + // Start listening for the window creation 536 + app.on('browser-window-created', onCreated); 537 + 538 + // Return allow with overridden options 539 + return { 540 + action: 'allow', 541 + overrideBrowserWindowOptions: winOptions 542 + }; 374 543 }); 375 544 376 545 // TODO: this should be pref'd ··· 436 605 const key = msg.hasOwnProperty('name') ? msg.name : null; 437 606 438 607 if (key != null) { 439 - for (const [id, w] of _windows) { 440 - console.log('win?', w.source, msg.source, w.params.key, key); 441 - if (w.source == msg.source && w.params.key == key) { 442 - console.log('FOUND WINDOW FOR KEY', key); 443 - const bw = BrowserWindow.fromId(id); 444 - let r = false; 445 - try { 446 - modWindow(bw, msg.params); 447 - r = true; 448 - } 449 - catch(ex) { 450 - console.error(ex); 451 - } 452 - ev.reply(msg.replyTopic, { output: r }); 608 + const existingWindow = windowManager.findWindowByKey(msg.source, key); 609 + if (existingWindow) { 610 + console.log('FOUND WINDOW FOR KEY', key); 611 + const bw = existingWindow.window; 612 + let r = false; 613 + try { 614 + modWindow(bw, msg.params); 615 + r = true; 616 + } 617 + catch(ex) { 618 + console.error(ex); 453 619 } 620 + ev.reply(msg.replyTopic, { output: r }); 454 621 } 455 622 } 456 623 }); ··· 460 627 console.log('window-open', msg); 461 628 462 629 const { url, options } = msg; 463 - const win = new BrowserWindow({ 464 - width: options.width || APP_DEF_WIDTH, 465 - height: options.height || APP_DEF_HEIGHT, 630 + 631 + // Check if window with this key already exists 632 + if (options.key) { 633 + const existingWindow = windowManager.findWindowByKey(msg.source, options.key); 634 + if (existingWindow) { 635 + console.log('Reusing existing window with key:', options.key); 636 + existingWindow.window.show(); 637 + return { success: true, id: existingWindow.id, reused: true }; 638 + } 639 + } 640 + 641 + // Prepare browser window options 642 + const winOptions = { 643 + ...options, // Pass all options to support any BrowserWindow constructor param 644 + width: parseInt(options.width) || APP_DEF_WIDTH, 645 + height: parseInt(options.height) || APP_DEF_HEIGHT, 466 646 show: options.show !== false, 467 647 webPreferences: { 648 + ...options.webPreferences, 468 649 preload: preloadPath 469 650 } 470 - }); 651 + }; 652 + 653 + // Make sure position parameters are correctly handled 654 + if (options.x !== undefined) { 655 + winOptions.x = parseInt(options.x); 656 + } 657 + if (options.y !== undefined) { 658 + winOptions.y = parseInt(options.y); 659 + } 660 + 661 + console.log('Creating window with options:', winOptions); 662 + 663 + // Create new window 664 + const win = new BrowserWindow(winOptions); 471 665 472 666 try { 473 667 await win.loadURL(url); 474 668 475 - // Add to windows cache 476 - _windows.set(win.id, { 669 + // Add to window manager with modal parameter 670 + windowManager.addWindow(win.id, { 477 671 id: win.id, 478 672 source: msg.source, 479 - params: { ...options, address: url } 673 + params: { 674 + ...options, 675 + address: url 676 + } 480 677 }); 678 + 679 + // Add escape key handler to all windows 680 + addEscHandler(win); 681 + 682 + // Set up modal behavior if requested 683 + if (options.modal === true) { 684 + win.on('blur', () => { 685 + console.log('window-open: blur for modal window', url); 686 + closeOrHideWindow(win.id); 687 + }); 688 + } 481 689 482 690 return { success: true, id: win.id }; 483 691 } catch (error) { ··· 500 708 } 501 709 502 710 win.close(); 711 + // WindowManager will automatically clean up on window close event 503 712 return { success: true }; 504 713 } catch (error) { 505 714 console.error('Failed to close window:', error); ··· 515 724 return { success: false, error: 'Window ID is required' }; 516 725 } 517 726 727 + // Get window data from manager to verify it exists 728 + const winData = windowManager.getWindow(msg.id); 729 + if (!winData) { 730 + return { success: false, error: 'Window not found in window manager' }; 731 + } 732 + 518 733 const win = BrowserWindow.fromId(msg.id); 519 734 if (!win) { 735 + // Clean up stale window reference 736 + windowManager.removeWindow(msg.id); 520 737 return { success: false, error: 'Window not found' }; 521 738 } 522 739 ··· 534 751 try { 535 752 if (!msg.id) { 536 753 return { success: false, error: 'Window ID is required' }; 754 + } 755 + 756 + // Get window data from manager to verify it exists 757 + const winData = windowManager.getWindow(msg.id); 758 + if (!winData) { 759 + return { success: false, error: 'Window not found in window manager' }; 537 760 } 538 761 539 762 const win = BrowserWindow.fromId(msg.id); 540 763 if (!win) { 764 + // Clean up stale window reference 765 + windowManager.removeWindow(msg.id); 541 766 return { success: false, error: 'Window not found' }; 542 767 } 543 768 ··· 557 782 return { success: false, error: 'Window ID is required' }; 558 783 } 559 784 785 + // Get window data from manager to verify it exists 786 + const winData = windowManager.getWindow(msg.id); 787 + if (!winData) { 788 + return { success: false, error: 'Window not found in window manager' }; 789 + } 790 + 560 791 const win = BrowserWindow.fromId(msg.id); 561 792 if (!win) { 793 + // Clean up stale window reference 794 + windowManager.removeWindow(msg.id); 562 795 return { success: false, error: 'Window not found' }; 563 796 } 564 797 ··· 582 815 return { success: false, error: 'Window ID is required' }; 583 816 } 584 817 818 + // Get window data from manager to verify it exists 819 + const winData = windowManager.getWindow(msg.id); 820 + if (!winData) { 821 + return { success: false, error: 'Window not found in window manager' }; 822 + } 823 + 585 824 const win = BrowserWindow.fromId(msg.id); 586 825 if (!win) { 826 + // Clean up stale window reference 827 + windowManager.removeWindow(msg.id); 587 828 return { success: false, error: 'Window not found' }; 588 829 } 589 830 ··· 603 844 return { success: false, error: 'Window ID is required' }; 604 845 } 605 846 847 + // Get window data from manager to verify it exists 848 + const winData = windowManager.getWindow(msg.id); 849 + if (!winData) { 850 + return { success: false, error: 'Window not found in window manager' }; 851 + } 852 + 606 853 const win = BrowserWindow.fromId(msg.id); 607 854 if (!win) { 855 + // Clean up stale window reference 856 + windowManager.removeWindow(msg.id); 608 857 return { success: false, error: 'Window not found' }; 609 858 } 610 859 ··· 616 865 } 617 866 }); 618 867 868 + // Add a window-exists handler to check if a window is still valid 869 + ipcMain.handle('window-exists', async (ev, msg) => { 870 + console.log('window-exists', msg); 871 + 872 + try { 873 + if (!msg.id) { 874 + return { exists: false, error: 'Window ID is required' }; 875 + } 876 + 877 + // Check if the window exists in the window manager 878 + const winData = windowManager.getWindow(msg.id); 879 + if (!winData) { 880 + return { exists: false }; 881 + } 882 + 883 + // Double-check that the window object is still valid 884 + const win = BrowserWindow.fromId(msg.id); 885 + if (!win || win.isDestroyed()) { 886 + // Clean up stale window reference 887 + windowManager.removeWindow(msg.id); 888 + return { exists: false }; 889 + } 890 + 891 + return { exists: true }; 892 + } catch (error) { 893 + console.error('Failed to check if window exists:', error); 894 + return { exists: false, error: error.message }; 895 + } 896 + }); 897 + 619 898 const modWindow = (bw, params) => { 620 899 if (params.action == 'close') { 621 900 bw.close(); ··· 681 960 // esc handler 682 961 // TODO: make user-configurable 683 962 const addEscHandler = bw => { 684 - console.log('adding esc handler'); 963 + console.log('adding esc handler to window:', bw.id); 685 964 bw.webContents.on('before-input-event', (e, i) => { 686 - //console.log('BIE', i.type, i.key); 687 965 if (i.key == 'Escape' && i.type == 'keyUp') { 688 - console.log('webcontents.onBeforeInputEvent(): esc'); 966 + console.log('Escape key pressed in window:', bw.id); 967 + 968 + // Always trigger close/hide on Escape, just like the original code 969 + console.log('Closing or hiding window on escape'); 689 970 closeOrHideWindow(bw.id); 690 971 } 691 972 }); 692 973 }; 693 974 694 - // configure windows opened by renderers 695 - const winOpenHandler = (source, details) => { 696 - console.log('WINOPENHANDLER', source, details); 697 - 698 - /* 699 - // TODO: do something that allows popping out 700 - // into default browser 701 - if (details.url.startsWith('http')) { 702 - shell.openExternal(details.url); 703 - return { action: 'deny' }; 704 - } 705 - */ 706 - 707 - const params = {}; 708 - details.features.split(',') 709 - .map(entry => entry.split('=')) 710 - // TODO: ugh 711 - .map(entry => { 712 - entry[1] = (entry[1] === 'false') ? false : true; 713 - return entry; 714 - }) 715 - .forEach(entry => params[entry[0]] = entry[1]); 716 - 717 - console.log('params', params); 718 - 719 - const overrides = { 720 - devTools: true, //DEBUG || params.debug, 721 - skipTaskbar: true, // TODO 722 - autoHideMenuBar: false, // TODO 723 - //titleBarStyle: 'hidden', // TODO 724 - webPreferences: { 725 - preload: preloadPath 726 - } 727 - }; 728 - 729 - smash(params, overrides, 'show', null, true); 730 - 731 - // keys are used to force singleton windows 732 - // TODO: this doesn't really do anything rn 733 - const key = params.hasOwnProperty('key') ? params.key : null; 734 - if (key != null) { 735 - _windows.forEach((w) => { 736 - if (w.source == source && w.params.key == key) { 737 - console.log('WINDOW ALREADY EXISTS FOR KEY', key); 738 - //id = w.id; 739 - } 740 - }); 741 - } 742 - 743 - // TODO: unhack 744 - const onBrowserWinCreated = (e, bw) => { 745 - console.log('onBrowserWinCreated', bw.id); 746 - app.off('browser-window-created', onBrowserWinCreated); 747 - 748 - // Capture new content windows created from this content window 749 - // (not firing sometimes, wtf? maybe in debug/not mode?) 750 - bw.webContents.on('did-create-window', (w, d) => { 751 - console.log('DID-CREATE-WINDOW', w, d); 752 - }); 753 - 754 - const didFinishLoad = () => { 755 - console.log('DID-FINISH-LOAD()'); 756 - 757 - // TODO: unhack 758 - const url = bw.webContents.getURL(); 759 - 760 - console.log('dFL(): url', url, details.url); 761 - console.log('dfl', bw.id); 762 - 763 - if (url == details.url) { 764 - bw.webContents.off('did-finish-load', didFinishLoad); 765 - 766 - //params.address = url; 767 - 768 - addEscHandler(bw); 769 - 770 - winDevtoolsConfig(bw); 771 - 772 - // don't do this in detached debug mode, devtools steals focus 773 - // and closes everything 😐 774 - // TODO: fix w/ devtoolsIsFocused() 775 - // - enumerat windows 776 - // - find devtools 777 - // - if exists and has focus, then bail 778 - // TODO: should be opener-configurable param 779 - if (!DEBUG) { 780 - bw.on('blur', () => { 781 - console.log('dFL.onBlur() for', url); 782 - closeOrHideWindow(bw.id); 783 - }); 784 - } 785 - 786 - // post actual close clean-up 787 - bw.on('closed', () => { 788 - console.log('dFL.onClosed: deleting ', bw.id, ' for ', url); 789 - 790 - // unregister any shortcuts this window registered 791 - const isPrivileged = url.startsWith(APP_PROTOCOL); 792 - if (isPrivileged) { 793 - unregisterShortcutsForAddress(url) 794 - } 795 - 796 - // remove from cache 797 - _windows.delete(bw.id); 798 - 799 - bw = null; 800 - }); 801 - 802 - // add to cache 803 - _windows.set(bw.id, { 804 - id: bw.id, 805 - source, 806 - params 807 - }); 808 - 809 - /* 810 - // send synthetic msg to source, notifying window was opened 811 - pubsub.publish(source, scopes.SELF, 'onWindowOpened', { 812 - url, 813 - key 814 - }); 815 - */ 816 - } 817 - }; 818 - 819 - bw.webContents.on('did-finish-load', didFinishLoad); 820 - }; 821 - 822 - app.on('browser-window-created', onBrowserWinCreated); 823 - 824 - console.log('OVERRIDES', overrides); 825 - 826 - return { 827 - action: 'allow', 828 - overrideBrowserWindowOptions: overrides 829 - }; 830 - }; 975 + // Nothing here - removed old window handler code 831 976 832 977 // show/configure devtools when/after a window is opened 833 978 const winDevtoolsConfig = bw => { ··· 859 1004 860 1005 let retval = false; 861 1006 862 - if (params.hasOwnProperty('id') && _windows.has(params.id)) { 1007 + if (params.hasOwnProperty('id') && windowManager.getWindow(params.id)) { 863 1008 console.log('closeWindow(): closing', params.id); 864 1009 865 - const entry = _windows.get(params.id); 1010 + const entry = windowManager.getWindow(params.id); 866 1011 if (!entry) { 867 1012 // wtf 868 1013 return; ··· 881 1026 }; 882 1027 883 1028 const closeOrHideWindow = id => { 884 - console.log('CLOSEORHIDEWINDOW', id); 1029 + console.log('CLOSE OR HIDE WINDOW CALLED FOR ID:', id); 885 1030 886 - const win = BrowserWindow.fromId(id); 887 - if (win.isDestroyed()) { 888 - console.log('window already dead'); 889 - return; 890 - } 1031 + try { 1032 + const win = BrowserWindow.fromId(id); 1033 + if (!win || win.isDestroyed()) { 1034 + console.log('Window already destroyed or invalid'); 1035 + return; 1036 + } 891 1037 892 - const entry = _windows.get(id); 1038 + const entry = windowManager.getWindow(id); 1039 + console.log('Window entry from manager:', entry); 893 1040 894 - if (!entry) { 895 - console.log('window not in cache, so closing (FIXME: should be in cache?)'); 896 - win.close(); 897 - return; 898 - } 1041 + if (!entry) { 1042 + console.log('Window not found in window manager, closing directly'); 1043 + win.close(); 1044 + return; 1045 + } 899 1046 900 - const params = entry.params; 1047 + const params = entry.params; 1048 + console.log('Window parameters:', params); 901 1049 902 - if (params.keepLive == true) { 903 - console.log('closeOrHideWindow(): hiding ', params.address); 904 - win.hide(); 1050 + // Check if window should be hidden rather than closed 1051 + // Either keepLive or modal parameter can trigger hiding behavior 1052 + if (params.keepLive === true || params.modal === true) { 1053 + console.log(`HIDING window ${id} (${params.address}) - modal: ${params.modal}, keepLive: ${params.keepLive}`); 1054 + win.hide(); 1055 + } else { 1056 + // close any open windows this window opened 1057 + closeChildWindows(params.address); 1058 + console.log(`CLOSING window ${id} (${params.address})`); 1059 + win.close(); 1060 + } 1061 + 1062 + console.log('closeOrHideWindow completed'); 1063 + } catch (error) { 1064 + console.error('Error in closeOrHideWindow:', error); 905 1065 } 906 - else { 907 - // close any open windows this window opened 908 - // TODO: need a "force" mode for this 909 - closeChildWindows(params.address); 910 - 911 - console.log('closeOrHideWindow(): closing ', params.address); 912 - win.close(); 913 - } 914 - console.log('DONE closeorhidewindow'); 915 1066 }; 916 1067 917 1068 const closeChildWindows = (aAddress) => { ··· 921 1072 return; 922 1073 } 923 1074 924 - for (const [id, entry] of _windows) { 925 - if (entry.source == aAddress) { 926 - const address = entry.params.address; 927 - console.log('closing child window', address, 'for', aAddress); 1075 + // Get all child windows from the window manager 1076 + const childWindows = windowManager.getChildWindows(aAddress); 1077 + 1078 + for (const child of childWindows) { 1079 + const address = child.data.params.address; 1080 + console.log('closing child window', address, 'for', aAddress); 928 1081 929 - // recurseme 930 - closeChildWindows(address); 1082 + // recurseme 1083 + closeChildWindows(address); 931 1084 932 - // close window 933 - BrowserWindow.fromId(id).close(); 1085 + // close window 1086 + const win = BrowserWindow.fromId(child.id); 1087 + if (win) { 1088 + win.close(); 934 1089 } 935 1090 } 936 1091 }; ··· 938 1093 /* 939 1094 // send message to all windows 940 1095 const broadcastToWindows = (topic, msg) => { 941 - _windows.forEach(win => { 942 - win.webContents.send(topic, msg); 943 - }); 1096 + for (const [id, _] of windowManager.windows) { 1097 + const win = BrowserWindow.fromId(id); 1098 + if (win) { 1099 + win.webContents.send(topic, msg); 1100 + } 1101 + } 944 1102 }; 945 1103 */ 946 1104 ··· 956 1114 957 1115 const onQuit = () => { 958 1116 console.log('onQuit'); 959 - // Close all persisent windows? 960 - 961 - app.quit(); 1117 + 1118 + // Notify all processes that the app is shutting down 1119 + pubsub.publish(systemAddress, scopes.GLOBAL, 'app:shutdown', { 1120 + timestamp: Date.now() 1121 + }); 1122 + 1123 + // Give windows a moment to clean up before forcing quit 1124 + setTimeout(() => { 1125 + app.quit(); 1126 + }, 100); 962 1127 }; 963 1128 964 1129 const smash = (source, target, k, d, noset = false) => {
+7
preload.js
··· 164 164 id 165 165 }); 166 166 }, 167 + exists: (id) => { 168 + console.log('window.exists', id); 169 + return ipcRenderer.invoke('window-exists', { 170 + source: sourceAddress, 171 + id 172 + }); 173 + }, 167 174 move: (id, x, y) => { 168 175 console.log('window.move', id, x, y); 169 176 return ipcRenderer.invoke('window-move', {