Skip to content

Add a Display Mode picker for choosing which screens are on - #1955

Open
zolferfigueiredo wants to merge 6 commits into
rxhanson:mainfrom
zolferfigueiredo:feature/display-mode
Open

zolferfigueiredo wants to merge 6 commits into
rxhanson:mainfrom
zolferfigueiredo:feature/display-mode

Conversation

@zolferfigueiredo

@zolferfigueiredo zolferfigueiredo commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

What

A Display Mode shortcut that works like Win+P on Windows: choose from the keyboard which screens are on, or mirror them.

  • Where: Settings > Shortcuts > ⋯ > Extra. The row shows when more than one screen is connected, and stays once it has a shortcut. There's no default shortcut.
  • The list: every combination of screens (Screen 1 Only, Screen 2 Only, Screens 1 + 2, ...), then Mirror All Screens. Each row shows the screen names under the numbers. With 5 or more screens the list is taller than the room on screen, so it scrolls and keeps the highlighted row in view; a list that fits has no scroll bar.
  • Screen numbers: while the list is open, each screen that's on shows its number in its middle, big, black with a white border. Numbers follow the Next Display order and don't change while a screen is off.
  • Keys: hold the modifiers and press the key again to move the highlight; let go to apply. A single press leaves the list open for the arrow keys, Return or a click. Esc or clicking elsewhere closes it without changes.
  • Icon: a new displayModeTemplate in the style of the other action icons (two screens in a frame).
  • URL: rectangle://execute-action?name=display-mode.
  • Docs: a section in README.md and README.zh-Hans.md, and display-mode in the URL action list.

Screenshot

image

How it works

  • Mirroring uses the public CoreGraphics display configuration API.
  • Turning a screen off uses the private CGSConfigureDisplayEnabled (falling back to SLSConfigureDisplayEnabled), looked up at runtime with dlsym, so nothing links against it. When neither exists, the list offers only every screen on plus Mirror All Screens.
  • Turning a screen off is applied for Rectangle's lifetime only (forAppOnly), so quitting Rectangle turns it back on. If every screen still on is disconnected, the screens Rectangle turned off come back on.
  • All of it lives in Rectangle/DisplayMode.swift. Elsewhere the change is the new action, one early return in ShortcutManager.execute, and the conditional row in the Extra section. The picker reuses StackBadgeManager.navigationKey and ScreenDetection.order.

Translations

The 5 new strings are translated into 12 languages: de, es, es-419, fa, fr, hi, it, ja, pt-BR, uk, zh-Hans and zh-Hant. English, Spanish (es, es-419), Brazilian Portuguese (pt-BR) and Italian (it) have been checked and are marked translated. The others are marked needs_review in the catalog until a native speaker checks them; corrections are welcome.

Tests

  • xcodebuild test: all 730 tests pass, including new DisplayModeTests for the option order, the per-screen configuration, a 32-row list (5 screens) that stays on screen and scrolls to its last row, and a list that fits with no room kept for a scroll bar.
  • Tried on a Mac with 3 screens (built-in plus two externals): the list opens, the shortcut moves the highlight, and the screen numbers show.
  • Not confirmed on hardware yet: that turning a screen off takes effect on current macOS, and what happens when the screen with the menu bar is turned off.

Known limits

  • Screen names come from macOS, so two identical monitors show macOS's own "(1)" and "(2)", which can differ from the screen numbers here.

A Win+P-style shortcut, in Settings > Shortcuts > ⋯ > Extra. The row
shows when more than one screen is connected or a shortcut is set.

The shortcut opens a list of every combination of screens (Screen 1
Only, Screens 1 + 2, ...) plus Mirror All Screens, and shows each
screen's number in the middle of that screen. Pressing it again while
holding the modifiers moves the highlight; letting go applies it. A
single press leaves the list open for the arrow keys, Return or a click.

Mirroring uses the public CoreGraphics API. Turning a screen off uses
the private CGSConfigureDisplayEnabled call, looked up at runtime: when
it's missing, only all screens and mirroring are offered. Screens turned
off come back when Rectangle quits, or when every screen still on is
disconnected.

Also available as rectangle://execute-action?name=display-mode.
@zolferfigueiredo
zolferfigueiredo marked this pull request as draft November 5, 2026 16:37
Five or more screens give 32+ rows, which grew the list past the bottom
of the screen. The list now stops at the room below the screen's number
(at least 5 rows) and scrolls, and the highlighted row is scrolled into
view as the shortcut or the arrow keys move it.

DisplayModePanel is no longer file-private, so a test can open a 32-row
list and check it fits on the screen and scrolls to the last row.
The list was built while the scroll view still kept room for a classic
scroll bar, so its rows came out about 15 pt narrower than the list and
left an empty strip on the right, even with 3 screens where nothing
scrolls. The scroller is now only turned on when the list is cut off,
and the rows take their width after the scroll view has laid out.
The 5 Display Mode strings stay translated in English, Spanish (es) and
Brazilian Portuguese (pt-BR), which have been checked. The other 9
languages are marked needs_review until a native speaker checks them.
Only the state changes; the app keeps showing every translation.
They match the checked Spanish (es) strings word for word.
Modalità schermo, Duplica tutti gli schermi, Solo schermo %lld, Schermi %@ and Schermo %lld, worded like the existing Italian (schermo for display), and marked translated.
@rxhanson

rxhanson commented Nov 6, 2026

Copy link
Copy Markdown
Owner

Thanks for continuing to contribute! I'm currently cleaning up some of the commonly tread paths in Rectangle, and I'll let you know when I'm done so the conflicts can be resolved all at once instead of continuously. At that time, I'll be working through my backlog of pull requests to review.

@zolferfigueiredo

Copy link
Copy Markdown
Contributor Author

Amazing! Take your time. Let me know when I should fix the conflicts;
My pleasure to contribute to this project;

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.

2 participants