Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Teleport

The entire bottom edge of your top display now leads to your bottom display.

A tiny native macOS menu bar utility for vertically stacked monitors of different widths.

Platform Swift Dependencies License

Teleport menu bar panel showing the detected display mapping

The problem

Stack a wide display above a narrower one and macOS will only let your cursor cross where the two displays geometrically overlap.

 ┌──────────────────────────────────────────┐
 │                                          │
 │                TOP DISPLAY               │
 │                                          │
 └───────────┬──────────────────┬───────────┘
   ✗ blocked │   ✓ native pass  │ ✗ blocked
             ├──────────────────┤
             │  BOTTOM DISPLAY  │
             └──────────────────┘

Push the cursor down from the far left or far right and it just… stops. You have to drag it back to the middle first. Every single time.

What Teleport does

 ┌──────────────────────────────────────────┐
 │                                          │
 │                TOP DISPLAY               │
 │                                          │
 └──────────────────────────────────────────┘
   ✓ mapped  │   ✓ native pass  │  ✓ mapped
             ├──────────────────┤
             │  BOTTOM DISPLAY  │
             └──────────────────┘

The whole bottom edge works. Your horizontal position is mapped proportionally, so leaving from 15% across the top display lands you 15% across the bottom one.

Movement Behaviour
Top centre → bottom Native macOS transition, untouched
Top far-left → bottom Cursor mapped proportionally onto the bottom display
Top far-right → bottom Cursor mapped proportionally onto the bottom display
Bottom → top Completely native macOS behaviour

Important

The asymmetry is the whole point. Teleport is not a bidirectional portal. It never intercepts, modifies or redirects upward cursor movement — going back up behaves exactly as it does with Teleport uninstalled. Toggling Teleport off restores stock macOS behaviour instantly.

Privacy

No data collection. No tracking. No analytics. No network access.

Teleport has no networking code at all — not disabled, not opt-out, absent. The binary does not even link a networking framework. There is no account, no telemetry, no crash reporter, no update check, no third-party SDK. Nothing about you or your machine leaves your Mac, because there is no code capable of sending it.

It does not read your keystrokes. The event tap is listenOnly and subscribes to mouse-moved and drag events only; keyboard events are never requested and Input Monitoring permission is never asked for.

The only thing Teleport stores is a handful of your own preferences in UserDefaults: the on/off state, the pairing mode, your display selection and the debug-logging flag.

Do not take our word for it

The whole point of publishing the source is that you can check, and this is a small enough app that checking is realistic — around 1525 lines of Swift, tests aside.

# Which frameworks does the shipped binary link? No network framework appears.
otool -L /Applications/Teleport.app/Contents/MacOS/Teleport

# Any networking code in the source at all?
grep -rE "URLSession|NWConnection|CFNetwork|socket|https?://" Teleport/

# Watch it do nothing on the network while you use it.
nettop -p $(pgrep -x Teleport)

Or hand the source to your LLM and ask it directly: "Does this app send any data anywhere? Does it read the keyboard?" Every file is in this repository, there is no build step that pulls in code you cannot see, and there are no dependencies.

Requirements

  • macOS 14 (Sonoma) or later
  • Apple silicon or Intel
  • Two or more displays, at least one arranged above another

Installation

  1. Download Teleport.app from the Releases page
  2. Move it to /Applications
  3. Launch it — a menu bar icon appears, no Dock icon, no window
  4. Click the icon and grant Accessibility permission

Building it yourself instead? See Building from source.

Using it

Click the menu bar icon to get the whole interface:

Control What it does
Enabled Master switch. Off means stock macOS, immediately
Mode Automatic (default) or Manual display pairing
Detected mapping The pair currently in effect
Bottom edge How much of the source edge macOS already handles natively
Source / Destination Manual pickers, shown in Manual mode
Launch at Login Register via SMAppService
Debug Logging Off by default; see Debug logging
Accessibility Granted / Not granted, with buttons to fix it

Automatic display detection

Teleport enumerates displays with CGGetOnlineDisplayList and reads each frame with CGDisplayBounds. It considers every ordered pair and keeps those where:

  • the candidate destination sits at or below the source's bottom edge (with a small tolerance for fractional scaling modes), and
  • the two overlap horizontally at all — with zero overlap there is no native transition to extend, and warping there would be surprising rather than helpful

The winner is the pair with the smallest vertical gap, breaking ties by widest native overlap, then largest source display, then display ID. Fully deterministic, and independent of enumeration order.

Nothing is hardcoded. Resolutions, scale factors, display IDs, menu bar location and which display is main are all read at runtime, and the whole thing is recalculated on every configuration change: connect, disconnect, rearrange, resolution change, scaling change, switching the main display, docking, undocking, sleep and wake. No restart required, ever.

Manual display selection

Automatic mode is the default. With three or more displays it can pick the wrong pair — switch Mode to Manual and choose the source (top) and destination (bottom) yourself.

Selections are stored by display ID. Unplug a selected display and Teleport says so in the panel and falls back to automatic detection, rather than quietly doing nothing.

Permissions

Teleport needs exactly one: Accessibility (System Settings → Privacy & Security → Accessibility).

Why it is needed, and what Teleport does not do

Teleport installs a listen-only CGEventTap on mouse-moved events. That tap is the only way to see that you are pushing the mouse downward while the window server is holding the cursor against a display boundary — at that moment the cursor position does not change at all, so nothing else would reveal your intent. Accessibility is the TCC permission that gates such a tap.

Teleport does not request Input Monitoring. That permission covers keyboard taps and raw IOHIDManager access, neither of which Teleport uses. Moving the cursor with CGWarpMouseCursorPosition requires no permission at all.

The tap is created with CGEventTapOptions.listenOnly, so events are never consumed, delayed or rewritten. Teleport only observes mouse motion and occasionally repositions the cursor. No keystrokes are observed, nothing is sent anywhere, and the app makes no network connections.

If permission is missing, the panel says so explicitly, explains why it is needed, and offers buttons to trigger the system prompt or jump straight to the right System Settings page. Teleport never fails silently.

How the cursor mapping works

Coordinate system — one canonical space, no conversions

Teleport uses a single coordinate system internally: CoreGraphics global display space — origin at the top-left of the main display, +X right, +Y down. It is shared by CGDisplayBounds, CGEvent.location and CGWarpMouseCursorPosition, so no conversion is ever needed.

NSScreen.frame uses the opposite vertical convention and is therefore never used for geometry — only for display names and backing scale factors, matched to a display by its NSScreenNumber. This removes an entire class of flip bugs.

In this system minY is a display's top edge and maxY its bottom edge. Everything is in points, so Retina, non-Retina and mixed scale factors need no special handling at all.

Detecting intent — five conditions, all required

On every mouse-moved or drag event, Teleport acts only when all of these hold:

  1. the cursor is on the source display
  2. it is within 2 pt of that display's bottom edge
  3. the event carries a downward delta (kCGMouseEventDeltaY > 0)
  4. downward travel accumulated inside the edge zone reaches 2 pt within a 300 ms window
  5. the cursor's X lies outside the natural horizontal overlap with the destination

Condition 3 rules out a cursor merely resting on the edge. Condition 4 rules out sliding sideways along it — pure horizontal motion produces deltaY == 0 and never accumulates. Condition 5 is the native-overlap rule: inside the overlap Teleport does nothing whatsoever and macOS performs its normal transition.

The mapping — proportional, then clamped
normalizedX  = (cursorX - source.minX) / source.width      // clamped to 0...1
destinationX = destination.minX + normalizedX * destination.width

destinationX is then clamped to stay at least 2 pt inside the destination's left and right edges, and the cursor lands 4 pt below its top edge — never exactly on a boundary, which could bounce it straight back.

Loop and jitter prevention

After a warp, events are ignored for 150 ms and the downward accumulator is reset. This absorbs the synthetic motion event the warp itself generates, plus any in-flight events still carrying the old location.

Teleport also calls CGAssociateMouseAndMouseCursorPosition(1) immediately after warping. Without it macOS briefly ignores mouse deltas so the pointer can "catch up" with its remembered position — which feels like a quarter-second freeze right after every transition.

Performance

Teleport is meant to be invisible. It is fully event driven: no polling of cursor position, no busy loops, no rendering when the panel is closed.

  • The event tap callback is a handful of float comparisons and returns immediately on the first failed condition
  • It runs on a dedicated userInteractive run loop thread, so a busy main thread can never delay it — macOS disables a tap whose callback is slow
  • Debug logging is gated by a single lock-protected boolean read, so it costs nothing when off
  • The only recurring timer in the whole app polls the Accessibility permission every 2 seconds, purely because TCC has no change notification — and it is invalidated the moment permission is granted

Debug logging

Off by default. Turn it on in the panel, then:

log stream --predicate 'subsystem == "com.avvamobile.macos-teleport"' --level debug

You get: detected displays and their frames, the selected pair, calculated overlap, blocked downward pushes, source and normalized coordinates, warps performed, warps suppressed by cooldown, and display configuration changes.

Known limitations

  • Bottom → top is intentionally unchanged. Coming back up behaves exactly like stock macOS, including being blocked where the displays do not overlap. Design requirement, not an oversight.
  • Only one source/destination pair is active at a time. Setups with several stacked pairs need manual selection.
  • Displays that merely touch horizontally (zero overlap) are not paired automatically — there is no native transition to extend. They can still be selected manually.
  • Mirrored displays are ignored; only the primary of a mirror set is a candidate.
  • Full-screen games that capture the mouse with CGAssociateMouseAndMouseCursorPosition(0) bypass cursor positioning entirely. Teleport has no effect there, by design.
  • Some secure input contexts (the login window, a secure text field held by another app) can make macOS disable the event tap. Teleport re-enables it automatically.
  • Not sandboxed, therefore not distributable on the Mac App Store — see Signing and notarization.

Building from source

git clone https://github.com/AvvaMobile/macos-teleport.git
cd macos-teleport
open Teleport.xcodeproj

Or from the command line:

xcodebuild -project Teleport.xcodeproj -scheme Teleport -configuration Release build
xcodebuild -project Teleport.xcodeproj -scheme Teleport test

Requires Xcode 15 or later. No third-party dependencies and no package manager — only Apple frameworks: SwiftUI, AppKit, CoreGraphics, ServiceManagement and os.

Install script

scripts/install.sh              # uses the first local signing identity
scripts/install.sh <identity>   # or pass one explicitly

Builds Release, signs, installs into /Applications and relaunches.

Tip

Signing with a stable identity matters during development. macOS keys the Accessibility permission to the app's code signature, so an unsigned or ad-hoc build makes you re-grant permission after every single rebuild.

Project layout

Teleport/
  App/          AppMain.swift · AppModel.swift · MenuBarView.swift
  Display/      DisplayInfo.swift · DisplayManager.swift · DisplayPair.swift
  Mouse/        CursorMapper.swift · MouseTransitionController.swift
  Permissions/  PermissionManager.swift
  Settings/     AppSettings.swift · LoginItemManager.swift
  Utilities/    Logger.swift
TeleportTests/  CursorMapperTests.swift · DisplayPairTests.swift
scripts/        install.sh

CursorMapper, DisplayInfo and DisplayPair are pure value logic — no AppKit, no global state — which is what the 40 unit tests exercise: proportional mapping, clamping, overlap calculation, top/bottom identification, mixed resolutions and scale factors, negative global coordinates, and the no-overlap, partial-overlap and full-overlap cases.

MouseTransitionController owns the event tap and is the only place that talks to CoreGraphics event APIs.

The bundle identifier is com.avvamobile.macos-teleport. If you fork this project, change PRODUCT_BUNDLE_IDENTIFIER in the build settings to your own before distributing a build.

Building a DMG

scripts/make-dmg.sh                          # build + sign, for local testing
scripts/make-dmg.sh --notarize AC_PASSWORD   # + notarize and staple, for release

The result is build/Teleport-<version>.dmg containing Teleport.app next to an /Applications symlink, so users drag one onto the other.

Set up the notarization credentials once:

xcrun notarytool store-credentials AC_PASSWORD \
    --apple-id you@example.com --team-id TEAMID \
    --password <app-specific-password>

Signing and notarization

Teleport builds with Hardened Runtime enabled and without the App Sandbox. That combination is required, not a shortcut: a sandboxed app cannot hold Accessibility permission, so an event tap is unavailable to it. No special entitlement is needed for the tap itself — Accessibility is granted by the user through TCC.

Warning

A DMG signed with anything other than a Developer ID Application certificate, or one that has not been notarized by Apple, will be rejected by Gatekeeper on other people's machines — they get "Teleport is damaged and can't be opened". An Apple Development certificate is enough to run the app on your own Mac and no more. Developer ID certificates require a paid Apple Developer Program membership.

Verify before publishing: spctl --assess --type execute -vv Teleport.app must print accepted.

Mac App Store distribution is not realistic here, because the store mandates the App Sandbox.

License

Teleport is source-available, not open source. The source is public and you may read, study, modify and share it — but commercial use requires a separate license.

Licensed under the PolyForm Noncommercial License 1.0.0.

✅ Free for personal use
✅ Free for noncommercial use — including charities, schools, public research and government institutions
✅ You may study, modify and redistribute the source for noncommercial purposes
❌ Commercial use requires a separate commercial license

Note

Because the PolyForm Noncommercial License restricts commercial use, it does not meet the OSI Open Source Definition. Teleport is deliberately not described as "open source".

Commercial licensing

Using Teleport commercially — including internal use at a for-profit company — requires a separate commercial license.

Contact: info@avvamobile.com


Copyright © 2025 Avva Mobile. All rights reserved.

About

Extends the entire bottom edge of your top display to your bottom display on macOS. Top → bottom enhanced, bottom → top stays completely native.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages