Skip to content

Immersive: the stream edge to edge on an iPad with a hardware keyboard - #161

Closed
lepeos wants to merge 2 commits into
Saffsanity:mainfrom
lepeos:immersive
Closed

lepeos wants to merge 2 commits into
Saffsanity:mainfrom
lepeos:immersive

Conversation

@lepeos

@lepeos lepeos commented Oct 18, 2026

Copy link
Copy Markdown

Immersive: on an iPad with a hardware keyboard, held sideways, the stream fills the whole screen and Sill's bar comes back only when you ask for it. It brings the iPhone's Larger Picture idea (#58, #79) to the iPad, which #79 left as it was.

What it does

  • Settings › This iPad › Immersive: off by default, stored on the device (Sill.immersive) and never sent. iPad only.
  • When it applies: the switch is on, a hardware keyboard is attached, the iPad is sideways, a window or the Desktop is streaming, VoiceOver is off, and Sill fills the screen (not Stage Manager or Split View). The bar, the 8 pt inset, the rounded frame, the status bar and the home indicator all go. The panel's new size goes out as one viewport, so the Mac refits a window the same way it does after a rotation.
  • Bringing the bar back: ⌃⌥⌘B, or a swipe down that starts within 20 pt of the top edge. The bar floats over the picture without resizing it, so no viewport is sent, and it hides after 4 s untouched. The top edge's system gestures are deferred while Immersive applies. The chord never reaches the Mac while Immersive applies, and is the Mac's otherwise.
  • Exit: a button in the revealed bar turns Immersive off at once.
  • Detaching the keyboard, turning upright, or stopping the stream: Immersive pauses at once and the layout goes back to main's.
  • The Desktop, edge to edge: the Desktop keeps the Mac screen's shape, so on a 4:3 iPad it letterboxes even with no chrome. While Immersive applies to the Desktop, the viewport sets deviceDesktop, and the host makes a display of the iPad's size the Mac's main display (Desktop in full screen on a display of the device's own size, curtain and sound like High Performance #156's DeviceDesktop). The Desktop then fills the iPad with no bars. An older host ignores the key and letterboxes as before.

The two commits

  1. "Host: the Desktop on a display of the device's own size": Desktop in full screen on a display of the device's own size, curtain and sound like High Performance #156's host side only (DeviceDesktop.swift, StreamCoordinator, WindowCatalog, Viewport.deviceDesktop), committed unchanged and under dr-apple's name. Desktop in full screen on a display of the device's own size, curtain and sound like High Performance #156's own second-tap full-screen mode on the device isn't taken, because Immersive covers it. If Desktop in full screen on a display of the device's own size, curtain and sound like High Performance #156 merges first, a rebase drops this commit.
  2. "iOS: Immersive on an iPad with a hardware keyboard": everything else. The rule is pure and Foundation-only (ImmersivePolicy.swift, checked in Tests/checks/immersive and in CI's matrix). The landscape layout keeps one view tree in all three states, so toggling never rebuilds the input view or drops the hardware keyboard's hold. The chord is handled through both a UIKeyCommand and pressesBegan, as ESC not passed through? #67's Esc is. The plan is in docs/immersive-plan.md, and the harness flags are in docs/DEVELOPMENT.md.

Compatibility

Additive only. Viewport.deviceDesktop is optional: an older host ignores it, and an older device never sends it. With the switch off, nothing changes for anyone.

Tested

  • Tests/checks/run-all.sh: all 45 passed. immersive: 71 checks, and 37 of 37 mutants caught.
  • The iOS app, Debug and Release for the simulator and Debug for a device; swift build -c release. Known warnings only.
  • The touch rig against main's: 72 of 72.
  • Harness photos with the switch off against main's build: sideways, sideways with no keyboard (and the switch on), upright, and upright with the drawer are identical. Sideways with the drawer open is within 16/255 at the edges of three thumbnail badges (anti-aliasing; the same in two runs of each build). Settings differs only by the new row.
  • Live on an iPad Pro 13" simulator against a host on loopback:
    • Immersed: viewport: 1376×1032 … device desktop, plus main's usual identical repeat. The host made the display once: "Device desktop 1376×1032 pt @2× … main", then "Streaming the device's own desktop … 2752×2064".
    • ⌃⌥⌘B: the bar was revealed and then hid itself, with no new viewport and no capture restart.
    • Exit: one viewport: 1360×930.
    • Quitting removed the display, and the real one was main again.
  • On an M4 iPad Pro 13" with the Magic Keyboard, against SillHost --virtual-display --pairing from this branch: the Desktop edge to edge, the bar on demand, and Exit back to the standard layout.

Not tested

Rotation and an Aa step while immersed on the Desktop; two devices at once; Stage Manager and Split View on a device; VoiceOver on a device; Sill.app (rather than the CLI host) from this branch. There's still no check for DeviceDesktop, as in #156. The plan's open questions cover the shortcut, keyboard-only, portrait, and the device desktop taking over the Mac's main display.

Happy to change anything. If you'd rather discuss the design in an issue first, I can open one.

dr-apple and others added 2 commits October 18, 2026 20:46
The host half of Saffsanity#156 (dr-apple), unchanged, for Immersive on the iPad; if
Saffsanity#156 lands first, a rebase drops this commit. Saffsanity#156's device-side full-screen
mode (a second tap on Desktop) is not taken: Immersive asks for the display
instead.

The device's viewport says so (Viewport.deviceDesktop, optional; an older host
ignores it and the Mac's own screen fills the picture). The host then makes a
virtual display of the device's size (÷ Aa's scale, at 2x) and makes it the
main display while the Desktop streams, as Screen Sharing's High Performance
mode does: menu bar and Dock move there, and the device shows a whole Mac
desktop edge to edge at its own resolution (DeviceDesktop.swift). A rotation
or an Aa step makes it again; anything else streaming, the flag off, the last
device leaving or Quit gives the main role back and removes it. The catalog
never takes one of Sill's virtual displays for the real screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings › This iPad › Immersive (off by default, Sill.immersive, never
sent). With it on, a hardware keyboard attached, the iPad sideways, a window
or the Desktop streaming, VoiceOver off and Sill filling the screen, the
landscape stream screen drops the bar, the inset, the frame, the status bar and
the home indicator: the panel is the whole screen, one viewport, and the Mac
refits a window as after a rotation. ⌃⌥⌘B or a swipe down from the top edge
brings the bar back over the picture without resizing it (no viewport); it
hides after 4 s untouched. Anything open over the stream holds it. Exit in the
revealed bar turns Immersive off at once. With the Desktop, the viewport asks
for a display of the iPad's own size (Viewport.deviceDesktop), so the Desktop
fills it with no letterbox; an older host letterboxes as before.

The rule is pure (ImmersivePolicy.swift, Tests/checks/immersive, in CI's
matrix); the layout keeps one view tree in all three states, so the input view
and the hardware keyboard's hold survive every toggle. The touch rig gains the
top edge's scenarios. docs/immersive-plan.md has the rule, the open questions
and the results; docs/DEVELOPMENT.md the harness's flags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Saffsanity

Copy link
Copy Markdown
Owner

From a pass over all the open pull requests (nothing is decided yet): this carries #156's host half as its first commit, and #156's own device half (a second tap on Desktop for full screen) conflicts with Immersive in StreamScreen.swift, so the device desktop should land once, as you and #156's author already agreed; #66 (and #72 and #73, built on it) replaces the catalog's display rule (its realDisplays and desktopDisplayID against that host commit's Sill-display filter and the display it makes main), so the second to land reconciles the two. #94 adds a Sound button to the same bar, so with both the revealed bar holds 8 buttons for MacMenuButton.fits; #147 hands every key to an input method while it composes, at the top of the same pressesBegan, so the two need one agreed order there; #158 adds its own hide-the-bar mode and visionOS input in the same files, and #155's iOS 16 target would reject the two-parameter onChange. The rest (#93, #95, #96, #99, #107, #111, #117, #122, #150) is the ci.yml mutants line or StreamScreen.swift, where both sides stay.

@Saffsanity

Copy link
Copy Markdown
Owner

The decision on hiding the bar is on #92: a toggle in Settings on the device, and a shake of the device turns it off so the bar comes back. Immersive is the closest to that and would take the shake as its way back beside the key command.

Saffsanity added a commit that referenced this pull request Oct 19, 2026
The plan gains the section on #66 (DisplayInfo's shape, its kind 40),
#156 and #161 (the device desktop as main) and #158 (a coordinator per
session); CLAUDE.md's Current step gains this change's entry, its results
to follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lepeos

lepeos commented Nov 5, 2026

Copy link
Copy Markdown
Author

Thanks for the pass over the open PRs, and for the decision on #92. #197 already does that decision in full: the Settings switch, the shake, a button and ⇧⌘B as other ways back (which covers @dr-apple's point there), on iPhone as well as iPad, with or without a keyboard. Immersive is narrower than that, so I'm closing this rather than adding a second way to hide the bar.

The one thing #161 had that #197 doesn't is the Desktop edge to edge. On a 4:3 iPad the Mac's 16:9 screen still letterboxes with the bar hidden. That part moves to #312. It's built on #197 and carries @dr-apple's host commit from #156 unchanged and under their name. With Hide Top Bar on, the Desktop streaming and the device sideways, the viewport asks for deviceDesktop, and the Mac makes a display of the device's size for it. Opening Settings or the drawer, which brings the bar back, keeps the size, so the Mac doesn't tear its display down and make it again for a visit to Settings.

While I have you, a question: what's the reasoning behind the Keyboard toggle being off by default the first time you open a Mac in Sill? Is off deliberate (keeping the picture larger, say), or just where it started?

@lepeos lepeos closed this Nov 5, 2026
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