Skip to content

feat(Zoom): Live Zoom mode — real-time screen magnification#32

Merged
07JP27 merged 15 commits into
07JP27:mainfrom
yusufk:feature/live-zoom
May 27, 2026
Merged

feat(Zoom): Live Zoom mode — real-time screen magnification#32
07JP27 merged 15 commits into
07JP27:mainfrom
yusufk:feature/live-zoom

Conversation

@yusufk

@yusufk yusufk commented May 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements Live Zoom using ScreenCaptureKit's SCStream for continuous 60fps frame capture with real-time pan/zoom controls.

Changes

  • New hotkey: ⌃⇧1 (Control+Shift+1) activates Live Zoom
  • Real-time screen content updates (videos, terminals, animations keep playing)
  • Mouse movement pans the zoomed viewport
  • Scroll wheel / ↑↓ arrow keys zoom in/out (1.0x–8.0x range)
  • Click enters Draw mode (freezes current frame as snapshot)
  • Escape / right-click exits
  • Own overlay window excluded from capture to prevent infinite mirror effect
  • Zero-copy rendering via IOSurface for GPU-backed display

Technical Details

  • LiveZoomWindowController manages SCStream lifecycle and overlay window
  • LiveZoomView renders frames via CALayer.contents with ZoomMath contentsRect
  • Stream excludes all app-owned windows (queried after overlay creation)
  • Reuses existing OverlayWindow, ZoomMath, and Settings infrastructure
  • Default hotkey stored in UserDefaults, configurable via Settings

Also included

  • Lowered deployment target from macOS 26 to macOS 15 (all APIs available since macOS 13+)
  • Clarified contributor build instructions in README

Testing

  • Verified on macOS 15.7.7 (Sequoia)
  • Confirmed: live content updates, pan/zoom controls, Draw mode transition, no infinite mirror

yusufk added 3 commits May 23, 2026 17:47
All APIs used (ScreenCaptureKit, SCScreenshotManager, CGPreflightScreenCaptureAccess)
are available since macOS 13+. Allows contributors on current macOS to build and run.
Unsigned builds fail with -10825 on macOS. Contributors must set their
own team ID in project.yml for a valid code signature.
Implements Live Zoom using ScreenCaptureKit SCStream for continuous
60fps frame capture with pan/zoom controls.

- New hotkey: ⌃⇧1 (Control+Shift+1) activates Live Zoom
- Real-time screen content updates (videos, terminals keep playing)
- Mouse movement pans, scroll wheel/arrow keys zoom in/out
- Click enters Draw mode (freezes current frame)
- Escape/right-click exits
- Own overlay window excluded from capture to prevent infinite mirror
- Zero-copy rendering via IOSurface for GPU-backed display

New files:
- LiveZoomWindowController.swift — SCStream lifecycle and window management
- LiveZoomView.swift — CALayer-based rendering with ZoomMath integration

Modified:
- AppDelegate: toggleLiveZoomMode() with Draw mode transition
- HotkeyManager: Live Zoom hotkey registration (ID 3)
- Settings: liveZoomHotkeyKeyCode/Modifiers with ⌃⇧1 default
- README: Feature coverage updated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a new “Live Zoom” overlay mode to ZoomacIt, implementing real-time magnification by streaming screen frames via ScreenCaptureKit and rendering them in an overlay with pan/zoom controls.

Changes:

  • Introduces LiveZoomWindowController + LiveZoomView to manage SCStream capture and render frames via CALayer.contents (IOSurface-backed).
  • Adds a new global hotkey (⌃⇧1) and persists its keycode/modifiers in Settings.
  • Lowers the macOS deployment target to 15.0 and updates project/docs (README + design plan).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/ZoomacIt/Overlay/LiveZoomWindowController.swift New controller to start/stop SCStream, exclude app windows, and feed frames to the view.
src/ZoomacIt/Overlay/LiveZoomView.swift New view to display live frames and implement pan/zoom + exit/enter-draw inputs.
src/ZoomacIt/Models/Settings.swift Adds persisted defaults/getters for the Live Zoom hotkey.
src/ZoomacIt/Core/HotkeyManager.swift Registers/unregisters the new Live Zoom hotkey and routes callbacks.
src/ZoomacIt/App/AppDelegate.swift Wires the new hotkey to a Live Zoom mode toggle and coordinates mode transitions.
src/ZoomacIt.xcodeproj/project.pbxproj Adds new source files to the project and lowers deployment target to macOS 15.0.
src/project.yml Lowers deployment target to macOS 15.0 for xcodegen generation.
README.md Marks Live Zoom as complete and adds contributor build/signing guidance.
design/LiveZoom-implementation-plan.md Adds an implementation plan/design note for Live Zoom.

Comment thread src/ZoomacIt/App/AppDelegate.swift Outdated
Comment thread src/ZoomacIt/App/AppDelegate.swift
Comment thread src/ZoomacIt/Overlay/LiveZoomWindowController.swift
Comment thread src/ZoomacIt/Overlay/LiveZoomWindowController.swift Outdated
Comment thread src/ZoomacIt/Overlay/LiveZoomWindowController.swift
Comment thread src/ZoomacIt/Overlay/LiveZoomView.swift
Comment thread src/ZoomacIt/Models/Settings.swift
Comment thread src/ZoomacIt/Models/Settings.swift
Comment thread src/ZoomacIt/Models/Settings.swift Outdated
- Move SCStream output to dedicated serial queue to avoid starving
  UI event handling at 60fps (dispatch only layer update to main)
- Set showsCursor=false to prevent double cursor (captured + system)
- Replace fragile 50ms sleep with retry loop for window exclusion
- Clamp initial zoomLevel to min/max range
- Add Live Zoom hotkey keys to resetToDefaults()
- Assign liveZoomController before showLiveZoom() so onShowFailed
  can properly clear the reference
@yusufk

yusufk commented May 24, 2026

Copy link
Copy Markdown
Contributor Author

Addressed Copilot review feedback in commit 7e74ebd:

  1. Controller assignment orderliveZoomController assigned before showLiveZoom() so onShowFailed can clear it
  2. ⏭️ Draw→LiveZoom transition — noted, will address if it causes issues in practice (current mode dismissal logic handles this)
  3. SCStream on main queue — moved to dedicated serial queue, only layer.contents update dispatched to main
  4. Double cursor — set showsCursor = false
  5. Fragile 50ms sleep — replaced with retry loop (up to 10×20ms) checking for our window in shareable content
  6. Zoom level clamping — initial value now clamped to min/max range
  7. resetToDefaults() — Live Zoom hotkey keys added to reset list
  8. ℹ️ Settings UI — hotkey is configurable via UserDefaults; full Settings UI will come with the Snip PR which adds a General tab for all hotkeys
  9. ℹ️ Tests — will add in a follow-up (SettingsTests extension)

@yusufk

yusufk commented May 24, 2026

Copy link
Copy Markdown
Contributor Author

Merge order note

These 4 PRs are now fully independent (each branched from main), but they all add to the same shared files (Settings.swift, HotkeyManager.swift, AppDelegate.swift, StatusBarController.swift). When merging sequentially, you'll hit additive conflicts that are trivial to resolve (just keep both sides).

Recommended merge order (least conflicts):

  1. feat(Zoom): Live Zoom mode — real-time screen magnification #32 Live Zoom ← merge first (largest feature, no conflicts with main)
  2. feat(Draw): pen cursor matches colour, size, and active shape tool #35 Draw Cursor ← no shared-file conflicts (only touches DrawingCanvasView.swift)
  3. feat: DemoType mode — simulated typing for demos #33 DemoType ← additive conflicts in shared files (keep both)
  4. feat: Snip mode — region screenshot to clipboard #34 Snip ← additive conflicts in shared files (keep both)

I've verified all 4 build independently and combine correctly. Happy to squash them into a single PR if you'd prefer a cleaner merge.

- Clear zoomSourceForDrawReturn before dismissing Draw mode when
  entering Live Zoom, preventing brief Still Zoom restoration

Also adds SettingsTests for Live Zoom hotkey:
- testDefaultLiveZoomHotkey (⌃⇧1)
- testLiveZoomHotkeyRoundTrip
- testLiveZoomHotkeyIndependentOfOtherHotkeys
- testLiveZoomHotkeyResetToDefaults

All 148 tests pass.
Comment thread src/project.yml
@yusufk

yusufk commented May 24, 2026

Copy link
Copy Markdown
Contributor Author

The deployment target of 15.0 is a minimum — it still builds and runs perfectly on macOS 26. This just allows users on older macOS versions to use the app too.

@07JP27

07JP27 commented May 24, 2026

Copy link
Copy Markdown
Owner

The deployment target of 15.0 is a minimum — it still builds and runs perfectly on macOS 26. This just allows users on older macOS versions to use the app too.

@yusufk
Thanks, and you're right that it still builds and runs on macOS 26. My concern is a bit different, though.

The deployment target controls what builds, but what I really care about is the support guarantee of the released app. My only test environment is macOS 26, so I can verify that the app behaves to spec on 26, but I have no way to test 15 through 25. This app leans heavily on version-sensitive APIs, and these areas have changed behavior across macOS releases before. So "the API exists since macOS 13" doesn't fully reassure me that it works correctly on every version in between.

That said, I don't think we have to choose between "revert to 26" and "guarantee 15–26." I'd be fine keeping the target at 15 as long as we clearly separate the build floor from the support scope — i.e. document in the README that the minimum build target is macOS 15, but the app is tested and officially supported on macOS 26 only, with earlier versions working possibly but not guaranteed.

That way contributors on current macOS can still build and run, and I'm not on the hook for versions I can't test. Would you be okay with that approach?

@yusufk

yusufk commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

Hi,

Thanks for the feedback and I understand the concern. I also agree with the approach and will adjust the readme when I get a moment.

Just a note though, Apple went straight from MacOS 15 to 26, with 15 still likely being the most prominent version out in the market.

Thanks
Yusuf

@yusufk

yusufk commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

Done — updated the README to clarify: minimum build target is macOS 15, but officially supported on macOS 26 only. See commit 470cbef.

Add Live Zoom feature documentation to the VitePress docs site:
- Add Live Zoom hotkey and feature description to usage pages (EN/JA)
- Add Live Zoom section to zoom usage pages with controls and
  comparison table vs Still Zoom (EN/JA)
- Update index page Zoom feature description

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@07JP27

07JP27 commented May 25, 2026

Copy link
Copy Markdown
Owner

Done — updated the README to clarify: minimum build target is macOS 15, but officially supported on macOS 26 only. See commit 470cbef.

Thanks, I've updated the README and docs(include Japanese version) to reflect your changes.

@07JP27 07JP27 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@yusufk Could you take a look at my review comments? If these changes were intentional, please feel free to let me know.

Comment thread src/ZoomacIt/Models/Settings.swift Outdated
Comment thread src/ZoomacIt/Overlay/LiveZoomWindowController.swift
Comment thread src/ZoomacIt/Overlay/LiveZoomWindowController.swift Outdated
@yusufk

yusufk commented May 26, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all three review comments in commit 1546d17:

  1. ✅ Live Zoom hotkey changed to Ctrl+4 (matches Windows ZoomIt)
  2. ✅ Stream failure now calls dismiss() before onShowFailed() — no orphaned overlay window
  3. ✅ CIContext is now a lazy property instead of allocating per-call

@07JP27 07JP27 self-requested a review May 27, 2026 11:40
@07JP27

07JP27 commented May 27, 2026

Copy link
Copy Markdown
Owner

Fixed minor issues.

@07JP27 07JP27 merged commit 8d52717 into 07JP27:main May 27, 2026
1 check passed
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