Skip to content

feat: add Combined Display Mode#1763

Open
dooyeoung wants to merge 4 commits into
rxhanson:mainfrom
dooyeoung:feature/combined-display-mode
Open

feat: add Combined Display Mode#1763
dooyeoung wants to merge 4 commits into
rxhanson:mainfrom
dooyeoung:feature/combined-display-mode

Conversation

@dooyeoung
Copy link
Copy Markdown

Summary

  • Adds a Combined Display Mode setting that treats multiple monitors as a single virtual display
  • Window actions (left/right half, center, maximize, corners, etc.) operate across all monitors at once
  • Useful for portrait monitors placed side by side where centering across displays is desired

Settings

A new checkbox is added to the Settings window under the display-related options, separated by a divider:

☑ Treat multiple monitors as one
When using multiple monitors, treats them as a single display. Requires System Settings > Desktop & Dock > Displays have separate Spaces to be OFF.

Localized via NSLocalizedString with Korean translation in Main.xcstrings.

When enabled, the Next/Previous Display menu items are hidden.

Implementation

  • Defaults.combinedDisplayMode (BoolDefault) persists the setting
  • WindowCalculationParameters carries an optional combinedDisplayFrame: CGRect?
  • WindowManager computes the union of all screen visible frames and passes it through the calculation pipeline
  • CenterCalculation, CenterProminentlyCalculation, and LeftRightHalfCalculation updated to respect the combined frame
  • BestEffortWindowMover receives the combined frame as visibleFrameOfDestinationScreen to prevent single-screen clamping

Test plan

  • Enable Combined Display Mode in Settings
  • Verify Left Half / Right Half fills the left/right monitor entirely
  • Verify Center places window in the center of the combined display area
  • Verify Maximize fills the combined display area
  • Verify corners (Top Left, Top Right, etc.) work correctly
  • Verify Next/Previous Display menu items are hidden when mode is on
  • Disable Combined Display Mode and verify normal per-monitor behavior is restored

🤖 Generated with Claude Code

Add a setting to treat multiple monitors as a single virtual display,
so window actions (half, center, maximize, corners, etc.) operate across
all connected monitors at once.

Settings window: "Treat multiple monitors as one" checkbox with description,
separated by a divider line from other options. Localized via NSLocalizedString
with Korean translation in Main.xcstrings.

When enabled:
- All window calculations receive a combined bounding frame of all monitors
- BestEffortWindowMover uses the combined frame to avoid single-screen clamping
- Left/Right half, Center, CenterProminently respect the combined frame
- Next/Previous Display menu items are hidden

Implementation:
- Defaults.combinedDisplayMode (BoolDefault) persists the setting
- WindowCalculationParameters carries combinedDisplayFrame: CGRect?
- WindowManager computes the union of all screen visible frames when enabled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rxhanson
Copy link
Copy Markdown
Owner

rxhanson commented May 20, 2026

Thanks for contributing! I actually implemented this in Rectangle Pro and simply didn't have time yet to test it thoroughly, officially release it, and port it to Rectangle. When I implemented it, I had simply adjusted the NSScreen extension in ScreenDetection to have this:

    func adjustedVisibleFrame(_ ignoreTodo: Bool = false, _ ignoreStage: Bool = false) -> CGRect {
        var newFrame = visibleFrame
        
        if !NSScreen.screensHaveSeparateSpaces && Defaults.combinedDisplayMode.enabled {
            newFrame = NSScreen.screens.reduce(CGRect.null) { $0.union($1.visibleFrame) }
        }
...

That avoided touching the WindowManager and other specific calculation files.

There might be a reason why Claude took your path instead, but off the top of my head I'm not sure what it would be. Would you mind swapping out your implementation here with this and seeing if it does what you expect?

mgrv-dooyeoung and others added 3 commits May 21, 2026 10:09
Per maintainer feedback, compute the union of all screens inside
adjustedVisibleFrame so every calculation picks it up automatically
without threading a combinedDisplayFrame parameter through
WindowCalculationParameters and each individual calculation file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ents

When combined display mode is active, return the union immediately so
stage strip, todo sidebar, and screen edge gap adjustments (all derived
from self, a single NSScreen) are not incorrectly applied to the
multi-screen combined frame.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Per maintainer feedback, compute the union of all screens inside
adjustedVisibleFrame so every calculation picks it up automatically.
Early return after the union to avoid incorrectly applying per-screen
adjustments (stage strip, todo sidebar, edge gaps) to the combined frame.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dooyeoung
Copy link
Copy Markdown
Author

Thanks for contributing! I actually implemented this in Rectangle Pro and simply didn't have time yet to test it thoroughly, officially release it, and port it to Rectangle. When I implemented it, I had simply adjusted the NSScreen extension in ScreenDetection to have this:

    func adjustedVisibleFrame(_ ignoreTodo: Bool = false, _ ignoreStage: Bool = false) -> CGRect {
        var newFrame = visibleFrame
        
        if !NSScreen.screensHaveSeparateSpaces && Defaults.combinedDisplayMode.enabled {
            newFrame = NSScreen.screens.reduce(CGRect.null) { $0.union($1.visibleFrame) }
        }
...

That avoided touching the WindowManager and other specific calculation files.

There might be a reason why Claude took your path instead, but off the top of my head I'm not sure what it would be. Would you mind swapping out your implementation here with this and seeing if it does what you expect?

@rxhanson
Thanks for the context — helpful to see how you approached it in Rectangle Pro.

I'll be honest, I didn't fully understand the background behind the original implementation, which is probably why it ended up going a different direction.

Swapped it out and it works as expected. One small difference though: I used return instead of assigning to newFrame:

if !NSScreen.screensHaveSeparateSpaces && Defaults.combinedDisplayMode.enabled {
return NSScreen.screens.reduce(CGRect.null) { $0.union($1.visibleFrame) }
}

If we assign and continue, the rest of the function — Stage Manager strip, Todo sidebar, screen edge gaps — all apply based on self (a single NSScreen) to what is now a multi-screen union. The Todo sidebar width is calculated from self.visibleFrame.width but gets subtracted from the full combined width, which seemed off.

Copy link
Copy Markdown
Author

@dooyeoung dooyeoung May 21, 2026

Choose a reason for hiding this comment

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

preview image
Image

@rxhanson
Copy link
Copy Markdown
Owner

Great! I'll test this out soon and will plan on merging if nothing else comes up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants