Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 58 additions & 12 deletions MULTI_MONITOR.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ The Matrix Digital Rain now supports fullscreen across multiple displays using t
4. Check either:
- **Independent Instances** - for varied effects across screens
- **Uniform Config** - for synchronized effects across screens
5. Double-click anywhere on the canvas to activate fullscreen across all displays
5. Double-click anywhere on the canvas to activate multi-monitor mode
- The main window will attempt to enter fullscreen
- Additional windows will open on other displays
- **Important**: You must manually double-click each child window to enter fullscreen due to browser security restrictions

#### Via URL Parameters

Expand Down Expand Up @@ -100,8 +103,9 @@ The implementation consists of:
- Enumerates all connected displays
- Opens a new window on each display with current URL
- Each window initializes with its own random seed
4. BroadcastChannel coordinates fullscreen requests
5. Each window enters fullscreen independently
4. Main window enters fullscreen
5. Child windows log a message prompting user to double-click for fullscreen
6. User must manually double-click each child window to activate fullscreen

#### Uniform Mode Flow

Expand All @@ -113,8 +117,10 @@ The implementation consists of:
- Serializes current config to URL parameters
- Opens a new window on each display with serialized config
- All windows start with identical settings
4. BroadcastChannel coordinates fullscreen requests
5. Natural drift occurs from random glyph timing
4. Main window enters fullscreen
5. Child windows log a message prompting user to double-click for fullscreen
6. User must manually double-click each child window to activate fullscreen
7. Natural drift occurs from random glyph timing

#### Exit Flow

Expand All @@ -139,7 +145,7 @@ For uniform mode, the following config parameters are serialized to URL:
The system uses `BroadcastChannel("matrix-multi-monitor")` for coordination:

```javascript
// Request fullscreen on all windows
// Notify child windows (they must manually enter fullscreen via double-click)
{ type: "requestFullscreen" }

// Exit fullscreen on all windows
Expand All @@ -152,6 +158,8 @@ The system uses `BroadcastChannel("matrix-multi-monitor")` for coordination:
{ type: "windowClosed", screenIndex: <number> }
```

**Note**: Due to browser security restrictions, the `requestFullscreen` message cannot actually trigger fullscreen on child windows. It only serves as a notification. Users must manually double-click each window.

## Error Handling

The system gracefully handles various error conditions:
Expand All @@ -171,14 +179,51 @@ The system gracefully handles various error conditions:
- Shows alert: "Multi-monitor fullscreen requires at least 2 displays"
- Falls back to standard single-screen fullscreen

### Window Spawn Failure
- Handles popup blocker interference
- Logs failures to console
- Continues with successfully opened windows
### Fullscreen Request Failure
- Fullscreen must be triggered by a direct user gesture (click, key press)
- Programmatic fullscreen requests fail with "Permissions check failed"
- Child windows cannot enter fullscreen automatically via message passing
- Solution: Users must double-click each child window

## Backward Compatibility
## Browser Security Restrictions

Modern browsers enforce strict security policies around the Fullscreen API to prevent malicious websites from hijacking the user's screen. These restrictions affect multi-monitor fullscreen:

### User Gesture Requirement

The `requestFullscreen()` method can **only** be called in direct response to a user interaction event (click, key press, touch). The browser considers these as valid user gestures:

- ✅ Mouse clicks (`click`, `dblclick`)
- ✅ Keyboard events (`keydown`, `keyup`, `keypress`)
- ✅ Touch events (`touchend`)

The browser **does not** consider these as user gestures:

- ❌ Timers (`setTimeout`, `setInterval`)
- ❌ Promises resolving asynchronously
- ❌ Message passing (`postMessage`, `BroadcastChannel`)
- ❌ Page load events (`DOMContentLoaded`, `load`)
- ❌ Network responses (`fetch`, `XMLHttpRequest`)

The feature is fully backward compatible:
### Impact on Multi-Monitor Fullscreen

When the user double-clicks the main window:
1. ✅ The main window can enter fullscreen (direct user gesture)
2. ❌ Child windows cannot enter fullscreen via BroadcastChannel message (not a user gesture in that window)

This is why users must manually double-click each child window to activate fullscreen on multiple displays.

### Why This Matters

This security restriction prevents malicious scenarios like:
- Websites automatically entering fullscreen without user consent
- Phishing attacks that mimic system dialogs in fullscreen mode
- Unwanted fullscreen popups or advertisements
- Cross-window fullscreen hijacking attempts

The browser vendors (Chrome, Firefox, Safari) all enforce these restrictions to protect users.

## Backward Compatibility

- **Single-monitor systems**: Work exactly as before
- **Existing URL parameters**: All respected and functional
Expand Down Expand Up @@ -225,6 +270,7 @@ The feature is fully backward compatible:
3. **Popup Blockers**: May interfere with window spawning (users must allow popups)
4. **No Explicit Sync**: Uniform mode uses same initial config but doesn't maintain frame-perfect synchronization
5. **Wake Lock**: Each window manages its own wake lock independently
6. **Manual Fullscreen**: Due to browser security policies, each child window must be manually set to fullscreen by double-clicking it. Programmatic fullscreen requests across windows via BroadcastChannel are not allowed as they're not considered user gestures

## Future Enhancements

Expand Down
37 changes: 34 additions & 3 deletions js/fullscreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,16 +183,47 @@ export function setupFullscreenToggle(element) {
const success = await multiMonitorManager.spawnWindows(matrixConfig);

if (success) {
// Request fullscreen on all spawned windows
// Notify child windows (they must enter fullscreen manually via double-click)
await multiMonitorManager.requestFullscreenAll();
console.log("Multi-monitor windows opened. Double-click each window to enter fullscreen.");

// Request fullscreen on main window
try {
const result = requestFullscreen(element);
Comment on lines +190 to +192

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid requesting fullscreen after an awaited spawn

This requestFullscreen runs only after await multiMonitorManager.spawnWindows(...) earlier in the same double‑click handler. The updated docs in MULTI_MONITOR.md explicitly note that user gestures don’t survive async operations/promises, so the browser will reject this call (Permissions check failed) and the main window never enters fullscreen in multi‑monitor mode. Users then can’t fullscreen the main window without re-triggering the flow (which re-spawns windows). Consider issuing the fullscreen request before the awaited work or requiring a second, dedicated user gesture for the main window.

Useful? React with 👍 / 👎.

if (result && result.catch) {
result.catch((error) => {
console.error("Fullscreen request failed:", error.message);
});
}
} catch (error) {
console.error("Fullscreen request failed:", error.message);
}
} else {
// Fall back to single-screen fullscreen
console.warn("Multi-monitor fullscreen failed, falling back to single screen");
requestFullscreen(element);
try {
const result = requestFullscreen(element);
if (result && result.catch) {
result.catch((error) => {
console.error("Fullscreen request failed:", error.message);
});
}
} catch (error) {
console.error("Fullscreen request failed:", error.message);
}
}
} else {
// Normal single-screen fullscreen
requestFullscreen(element);
try {
const result = requestFullscreen(element);
if (result && result.catch) {
result.catch((error) => {
console.error("Fullscreen request failed:", error.message);
});
}
} catch (error) {
console.error("Fullscreen request failed:", error.message);
}
Comment on lines +191 to +226
Copy link

Copilot AI Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling pattern for fullscreen requests is duplicated three times with identical code blocks. This violates the DRY (Don't Repeat Yourself) principle and makes the code harder to maintain.

Consider extracting this into a helper function within the setupFullscreenToggle scope:

const safeRequestFullscreen = (element) => {
  try {
    const result = requestFullscreen(element);
    if (result && result.catch) {
      result.catch((error) => {
        console.error("Fullscreen request failed:", error.message);
      });
    }
  } catch (error) {
    console.error("Fullscreen request failed:", error.message);
  }
};

Then replace all three occurrences (lines 191-200, 204-213, 217-226) with calls to this helper function.

Copilot uses AI. Check for mistakes.
}
} else {
// In fullscreen, exit it
Expand Down
24 changes: 16 additions & 8 deletions js/multi-monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,32 +302,40 @@ export default class MultiMonitorManager {

/**
* Request fullscreen on all spawned windows
*
* NOTE: Due to browser security restrictions, child windows cannot enter
* fullscreen programmatically via BroadcastChannel. This method notifies
* child windows, but users must manually double-click each window to enter
* fullscreen mode.
*/
async requestFullscreenAll() {
// Use broadcast channel to tell all child windows to enter fullscreen
// Use broadcast channel to notify child windows
// They will log a message prompting the user to double-click
if (this.broadcastChannel) {
this.broadcastChannel.postMessage({ type: "requestFullscreen" });
}

// Child windows will handle their own fullscreen requests
// Note: Child windows cannot enter fullscreen automatically
return true;
}

/**
* Handle fullscreen request from child window
* This is called by child windows when they receive the broadcast
*
* NOTE: Due to browser security restrictions, fullscreen cannot be requested
* programmatically via BroadcastChannel as it's not considered a user gesture.
* Child windows must enter fullscreen manually (e.g., via double-click).
*/
static handleChildFullscreenRequest(canvas) {
const channel = new BroadcastChannel("matrix-multi-monitor");

channel.addEventListener("message", (event) => {
if (event.data.type === "requestFullscreen") {
// Request fullscreen on this child window
if (canvas.requestFullscreen) {
canvas.requestFullscreen().catch((err) => {
console.error("Fullscreen request failed:", err);
});
}
// Cannot request fullscreen programmatically via broadcast message
// as it's not considered a user gesture by the browser.
// Users should double-click each window to enter fullscreen.
console.log("Fullscreen request received. Double-click the window to enter fullscreen mode.");
} else if (event.data.type === "exitFullscreen") {
// Exit fullscreen on this child window
if (document.fullscreenElement) {
Expand Down
Loading