-
Notifications
You must be signed in to change notification settings - Fork 0
Add multi-monitor fullscreen support via Window Management API #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Add multiMonitorMode config parameter (none, multiple, uniform) - Add serializeConfig() function to export config as URL params - Add Window Management API integration in fullscreen.js - Add BroadcastChannel for coordinating fullscreen exit across windows - Add two new checkboxes in mode-display.js for multi-monitor modes - Add event handling in main.js for mode switching - Only one mode can be active at a time (mutual exclusion) - Double-click triggers multi-monitor fullscreen when mode enabled Co-authored-by: ap0ught <41078+ap0ught@users.noreply.github.com>
- Use screenLeft/screenTop with fallback to screen.left/top for better browser compatibility - Add window.open features for minimal chrome (no scrollbars, menubar, etc.) - Improve fullscreen timing with readyState check instead of fixed 500ms timeout - Add RADIANS_TO_DEGREES constant for better code readability - Reduce timeout from 500ms to 100ms with readyState check Co-authored-by: ap0ught <41078+ap0ught@users.noreply.github.com>
|
@copilot make sure to update your copilot logic and documentatiuon with your new understandings |
🎬 PR Preview Deployed!Your changes are now available for testing: Preview URL: https://ap0ught.github.io/matrix/pr-74/ Test Links:The preview will be updated automatically when you push new commits. Preview deployed from commit 0be801d |
- Document Window Management API integration pattern - Document BroadcastChannel coordination pattern - Document config serialization for uniform mode - Document mutually exclusive checkbox UI pattern - Document reliable async window initialization - Document window lifecycle management - Document browser compatibility patterns - Add lessons learned and future guidelines Co-authored-by: ap0ught <41078+ap0ught@users.noreply.github.com>
Updated Added documentation for:
|
|
@copilot resolve merge conflicts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds multi-monitor fullscreen support to the Matrix digital rain screensaver using the Window Management API. It introduces two new modes: "Multiple" (random variations per display) and "Uniform" (synchronized config across displays), enabling users to span the Matrix effect across all available displays.
Key Changes
- Added Window Management API integration to enumerate displays and spawn windows across multiple screens
- Implemented config serialization to pass identical settings to child windows in uniform mode
- Created BroadcastChannel-based coordination system for synchronized fullscreen exit across all windows
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
js/config.js |
Added multiMonitorMode parameter and serializeConfig() function to export config as URL parameters for child windows |
js/fullscreen.js |
Integrated Window Management API, BroadcastChannel coordination, and multi-monitor window lifecycle management |
js/mode-display.js |
Added mutually exclusive checkboxes for selecting multi-monitor mode with event handling |
js/main.js |
Added event handler for multi-monitor mode changes and URL parameter management |
.copilot/instructions.md |
Documented architectural patterns, implementation details, and lessons learned from the feature |
Comments suppressed due to low confidence (1)
js/fullscreen.js:85
- Unused function requestWindowManagementPermission.
async function requestWindowManagementPermission() {
js/fullscreen.js
Outdated
| if (newWindow.document.readyState === "complete") { | ||
| // Additional small delay to ensure renderer is ready | ||
| setTimeout(() => { | ||
| if (newWindow.document.documentElement.requestFullscreen) { | ||
| newWindow.document.documentElement.requestFullscreen().catch((err) => { | ||
| console.error("Failed to request fullscreen on child window:", err); | ||
| }); | ||
| } | ||
| }, 100); | ||
| } else { | ||
| newWindow.addEventListener("load", requestFullscreenWhenReady); |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for window reference becoming invalid: When a window is opened successfully with window.open(), it's added to the multiMonitorWindows array. However, if the user manually closes one of the child windows, the window reference remains in the array but becomes invalid. When requestFullscreenWhenReady tries to access newWindow.document, it could throw a cross-origin or invalid reference error if the window has been closed.
Consider wrapping the document access in a try-catch block to handle cases where the window has been closed or becomes inaccessible.
| if (newWindow.document.readyState === "complete") { | |
| // Additional small delay to ensure renderer is ready | |
| setTimeout(() => { | |
| if (newWindow.document.documentElement.requestFullscreen) { | |
| newWindow.document.documentElement.requestFullscreen().catch((err) => { | |
| console.error("Failed to request fullscreen on child window:", err); | |
| }); | |
| } | |
| }, 100); | |
| } else { | |
| newWindow.addEventListener("load", requestFullscreenWhenReady); | |
| try { | |
| // If the Operator (user) has closed this window, unplug it from the Matrix | |
| if (!newWindow || newWindow.closed) { | |
| const idx = multiMonitorWindows.indexOf(newWindow); | |
| if (idx !== -1) { | |
| multiMonitorWindows.splice(idx, 1); | |
| } | |
| return; | |
| } | |
| const doc = newWindow.document; | |
| if (doc && doc.readyState === "complete") { | |
| // Additional small delay to ensure renderer is ready | |
| setTimeout(() => { | |
| try { | |
| if (!newWindow || newWindow.closed) { | |
| return; | |
| } | |
| const childDoc = newWindow.document; | |
| const docEl = childDoc && childDoc.documentElement; | |
| if (docEl && docEl.requestFullscreen) { | |
| docEl.requestFullscreen().catch((err) => { | |
| console.error("Failed to request fullscreen on child window:", err); | |
| }); | |
| } | |
| } catch (err) { | |
| console.error("Failed to access child window for fullscreen:", err); | |
| } | |
| }, 100); | |
| } else { | |
| newWindow.addEventListener("load", requestFullscreenWhenReady); | |
| } | |
| } catch (err) { | |
| console.error("Failed to access child window document for fullscreen:", err); |
js/fullscreen.js
Outdated
| * Request Window Management API permission | ||
| * @returns {Promise<PermissionStatus|null>} Permission status or null if not supported | ||
| */ | ||
| async function requestWindowManagementPermission() { | ||
| if (!isWindowManagementSupported()) { | ||
| console.warn("Window Management API not supported"); | ||
| return null; | ||
| } | ||
|
|
||
| try { | ||
| const permission = await navigator.permissions.query({ name: "window-management" }); | ||
| return permission; | ||
| } catch (err) { | ||
| console.error("Failed to query window-management permission:", err); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| /** |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The requestWindowManagementPermission function is defined but never used in the code. The openMultiMonitorFullscreen function directly calls window.getScreenDetails() at line 177 without first checking or requesting permission using this function.
Either remove the unused function or integrate it into the flow before calling getScreenDetails() to properly handle permission requests.
| * Request Window Management API permission | |
| * @returns {Promise<PermissionStatus|null>} Permission status or null if not supported | |
| */ | |
| async function requestWindowManagementPermission() { | |
| if (!isWindowManagementSupported()) { | |
| console.warn("Window Management API not supported"); | |
| return null; | |
| } | |
| try { | |
| const permission = await navigator.permissions.query({ name: "window-management" }); | |
| return permission; | |
| } catch (err) { | |
| console.error("Failed to query window-management permission:", err); | |
| return null; | |
| } | |
| } | |
| /** |
js/fullscreen.js
Outdated
| async function exitMultiMonitorFullscreen() { | ||
| // Exit fullscreen on current window | ||
| if (document.fullscreenElement) { | ||
| try { | ||
| await document.exitFullscreen(); | ||
| } catch (err) { | ||
| console.error("Failed to exit fullscreen:", err); | ||
| } | ||
| } | ||
|
|
||
| // Broadcast exit message to other windows | ||
| if (multiMonitorBroadcast) { | ||
| multiMonitorBroadcast.postMessage({ type: "exit-fullscreen" }); | ||
| } | ||
|
|
||
| // Clean up windows | ||
| cleanupMultiMonitor(); | ||
|
|
||
| // Release wake lock | ||
| await releaseWakeLock(); | ||
| } |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential infinite recursion: When a child window calls exitMultiMonitorFullscreen() in response to a broadcast message (line 109), it broadcasts another "exit-fullscreen" message at line 153. This could cause all windows to repeatedly broadcast exit messages to each other.
Consider adding a flag or guard to prevent re-broadcasting when already in the process of exiting, or only broadcast from the window that initiated the exit (not child windows responding to the broadcast).
| const serializableParams = [ | ||
| "version", | ||
| "font", | ||
| "effect", | ||
| "numColumns", | ||
| "resolution", | ||
| "animationSpeed", | ||
| "forwardSpeed", | ||
| "cycleSpeed", | ||
| "fallSpeed", | ||
| "raindropLength", | ||
| "slant", | ||
| "bloomSize", | ||
| "bloomStrength", | ||
| "volumetric", | ||
| "fps", | ||
| "renderer", | ||
| "suppressWarnings", | ||
| ]; |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The serializeConfig function is missing several configuration parameters that are available in the codebase and should be serialized for uniform mode to work correctly. Missing parameters include:
glintColor/glintHSL- Used for glyph highlights (defined in paramMapping)cursorIntensity- Brightness multiplier for cursor glow (used in config)glyphIntensity- Brightness multiplier for glyphs (used in config)ditherMagnitude- Random per-pixel dimming (defined in defaults)glyphFlip- Horizontal glyph reflection (defined in defaults)glyphRotation- Angle to rotate glyphs (defined in defaults)isometric- Isometric view mode (defined in paramMapping)loops- Loop configuration (defined in paramMapping)
Without these parameters, child windows in uniform mode may not accurately replicate the parent window's visual configuration, leading to inconsistent appearance across displays.
| const values = config.cursorColor.values.join(","); | ||
| params.set(config.cursorColor.space === "hsl" ? "cursorHSL" : "cursorColor", values); | ||
| } | ||
|
|
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The glintColor serialization logic is missing from the config serialization function. The code handles backgroundColor, cursorColor, palette, and stripeColors, but doesn't handle glintColor which is a valid color parameter that should be serialized for uniform mode.
Add the following after line 841:
if (config.glintColor && config.glintColor.values) {
const values = config.glintColor.values.join(",");
params.set(config.glintColor.space === "hsl" ? "glintHSL" : "glintColor", values);
}
This follows the same pattern as the other color serializations.
| if (config.glintColor && config.glintColor.values) { | |
| const values = config.glintColor.values.join(","); | |
| params.set(config.glintColor.space === "hsl" ? "glintHSL" : "glintColor", values); | |
| } |
js/fullscreen.js
Outdated
| const requestFullscreenWhenReady = () => { | ||
| if (newWindow.document.readyState === "complete") { | ||
| // Additional small delay to ensure renderer is ready | ||
| setTimeout(() => { | ||
| if (newWindow.document.documentElement.requestFullscreen) { | ||
| newWindow.document.documentElement.requestFullscreen().catch((err) => { | ||
| console.error("Failed to request fullscreen on child window:", err); | ||
| }); | ||
| } | ||
| }, 100); | ||
| } else { | ||
| newWindow.addEventListener("load", requestFullscreenWhenReady); | ||
| } | ||
| }; | ||
| requestFullscreenWhenReady(); |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential infinite recursion issue: When requestFullscreenWhenReady is called and readyState is not "complete", it adds an event listener for the "load" event. However, if the load event has already fired (which can happen if the check is executed after the page loads), the event listener will never trigger, and the function will never be called again. While the current implementation has a fallback path where it checks readyState first, there's a potential issue if readyState is neither "complete" nor triggers a "load" event.
Consider adding a timeout or error handling to prevent indefinite waiting for fullscreen initialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot apply changes based on this feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feedback is not applicable to the current implementation. The requestFullscreenWhenReady function referenced in comment 2636704728 was part of the initial implementation (commit 5eccba9) that was replaced during the merge (commit c29c378) with master's MultiMonitorManager class architecture.
The current implementation in js/multi-monitor.js handles window spawning and fullscreen coordination differently, with proper error handling and state management built into the MultiMonitorManager class. The potential infinite recursion issue mentioned in the review does not exist in the current codebase.
…mplementation Resolved merge conflicts by accepting the master branch's comprehensive implementation: - Replaced single multiMonitorMode parameter with separate fullscreenMultiple and fullscreenUniform flags - Adopted MultiMonitorManager class architecture (js/multi-monitor.js) - Updated fullscreen.js to use multiMonitorManager reference - Updated main.js and mode-display.js for new API - Added multiMonitorChild and screenIndex internal flags - Integrated MULTI_MONITOR.md documentation - Added GitHub Pages deployment workflow - Added TESTING_SUMMARY.md Master branch has a more robust implementation with dedicated manager class, better separation of concerns, and comprehensive documentation. Co-authored-by: ap0ught <41078+ap0ught@users.noreply.github.com>
Merge conflicts resolved (commit c29c378). Adopted the master branch's superior implementation which includes:
The master branch's implementation provides better separation of concerns and a more robust event-driven API. |
|
@copilot apply changes based on the comments in this thread |
- Added cursorIntensity, glyphIntensity for brightness control - Added ditherMagnitude for random per-pixel dimming - Added glyphFlip for horizontal glyph reflection - Added glyphRotation for glyph angle rotation - Added isometric for isometric view mode - Added loops for loop configuration - Added glintColor serialization (HSL/RGB support) These parameters are now properly passed to child windows in uniform multi-monitor mode, ensuring consistent visual configuration across all displays. Addresses review comments 2636704719 and 2636704723. Co-authored-by: ap0ught <41078+ap0ught@users.noreply.github.com>
Applied changes from code review (commit 0e636e0). Addressed:
Not applicable (code replaced by master branch's MultiMonitorManager):
The uniform multi-monitor mode now properly serializes all visual configuration parameters. |
|
@copilot apply changes based on the comments in this thread |
Already addressed in commit 0e636e0. All review feedback from that thread has been applied:
The uniform multi-monitor mode now properly serializes all visual configuration parameters to ensure consistent appearance across all displays. |
Multi-Monitor Fullscreen Feature Implementation
This PR adds support for spanning the Matrix digital rain effect across multiple displays using the Window Management API.
Features Added
Two New Fullscreen Modes
1. Independent Instances (formerly "Multiple")
2. Uniform Config
UI Integration
Added "Multi-Monitor Fullscreen" section to the Matrix Mode panel with:
?fullscreenMultiple=trueor?fullscreenUniform=true)Technical Implementation
Architecture
The implementation uses a dedicated MultiMonitorManager class architecture that provides:
Files Modified/Added
js/multi-monitor.js (NEW - 450+ lines)
js/config.js (~110 lines modified)
fullscreenMultipleboolean flag for independent instances modefullscreenUniformboolean flag for uniform config modemultiMonitorChildinternal flag to identify child windowsscreenIndexparameter for tracking which screen a window is onserializeConfig()function to export config as URL parametersjs/fullscreen.js (~150 lines modified)
js/mode-display.js (~50 lines modified)
setMultiMonitorMode()methodjs/main.js (~40 lines modified)
MULTI_MONITOR.md (NEW)
TESTING_SUMMARY.md (NEW)
.copilot/instructions.md (+240 lines)
.github/workflows/gh-pages-deploy.yml (NEW)
.github/GITHUB_PAGES.md (NEW)
Total Changes: ~1,850 additions, ~285 deletions across 13 files
How It Works
Activation
?fullscreenMultiple=trueor?fullscreenUniform=true)Fullscreen Trigger
window.getScreenDetails()Coordination & Exit
Error Handling
Testing & Validation ✅
Manual Testing
?fullscreenMultiple=trueor?fullscreenUniform=true)Code Quality
Browser Compatibility
Security Summary
✅ No security vulnerabilities detected by CodeQL
Documentation Updates
Code Review Feedback Addressed
Addressed in this commit:
Not applicable (code replaced by master branch's MultiMonitorManager):
Usage Instructions
Merge Resolution
This PR was successfully merged with the master branch's enhanced implementation which provided:
MultiMonitorManagerclass architectureThe master branch's implementation replaced the initial simpler approach with a more maintainable and extensible solution.
Future Enhancements (Out of Scope)
Notes
Original prompt
This pull request was created as a result of the following prompt from Copilot chat.
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.