Repository navigation
Releases: kendalpercimoney/imac-second-display
Release list
v2.0.0 — the iMac's keyboard and mouse drive the Mac
Update both apps
The wire protocol has gained pointer, audio, keyboard and mouse, and decoder messages, so a 1.x client paired with this host shows no pointer at all. The host's panel says when the client cannot do something rather than leaving you to notice. main and the greedy branch are now the same code, so the protocol split mentioned in v1.3.0 is gone.
The iMac's keyboard and mouse drive the Mac
While the client's window is in front, the iMac's own keyboard and mouse control the Mac — and the pointer moves across both displays, off the edge of the virtual display and on to the Mac's own screen, the way a second monitor's would. Motion is sent as deltas rather than as a position on the iMac's screen, which is what lets it leave.
- Bringing the window to the front, or clicking the picture, hands them over. That click is not passed on.
- Control-Option-Esc gives them back, as does switching away.
- While handed over, Command-Q and Escape go to the Mac instead of quitting the client. Command-Tab, Command-Space and Mission Control never reach an app and stay with the iMac.
- The Mac needs the Accessibility permission; the panel says when it is missing and has an Allow button. Until it is granted, the client will not take the iMac's pointer away from it.
- Only the iMac that connected is listened to, and nothing can be left held down: the client sends what it holds four times a second and the Mac lets go of anything not listed.
No more moshing when scrolling or zooming
Every frame after a keyframe is a set of changes to the one before. When decoding fell behind, the client threw away a waiting frame and decoded the next one anyway, on top of the wrong picture — and the damage carried forward until the next keyframe, up to five seconds later. Measured with real frames really decoded, each scored against what it should have been:
| worst frame shown | damaged frames shown | |
|---|---|---|
| decode everything | 38.9 dB | 0 |
| drop one, carry on (1.x) | 16.7 dB | 49 |
| drop one, wait for a keyframe (2.0) | 38.9 dB | 0 |
A frame is now never decoded unless the one before it was, and the client asks for a keyframe at once. The picture holds still for a round trip instead of being wrong for seconds.
5 ms faster
Two encoder settings that had been on since the start were costing 5.4 ms of a 13.5 ms pipeline: VideoToolbox's low-latency rate controller and RealTime. They hid each other — with the rate controller on, RealTime measures as free — so they had only ever been measured together. Capture to decoded frame is now 8.0 ms, with the full requested bitrate and Baseline rather than Constrained Baseline.
Also new
- Audio from the Mac, uncompressed, with volume and a delay slider that can go negative.
- iMac brightness, set from the Mac.
- Video mode: a separate, higher bitrate on a switch you can flip mid-stream, at no cost in latency. Above 50 Mb/s the stream becomes H.264 Level 5, which an older iMac may decode in software; the panel's Decoder readout says which it got.
- Sleep: the stream comes back after the Mac wakes, and a socket that stops working is replaced rather than abandoned.
- A menu bar app, which says where it went when you open it.
- The panel names the interface and gives the exact command when the link's MTU is cutting the packet size, and counts packets this Mac could not send.
Installing
Neither app is notarised.
xattr -dr com.apple.quarantine LanScreenHost.app
On OS X 10.9, right-click the client and choose Open. Screen Recording and Accessibility are both tied to the code signature, so a downloaded host asks for them separately from one you built yourself.
| Architecture | Minimum OS | SHA-256 | |
|---|---|---|---|
LanScreenHost-v2.0.0.zip |
arm64 | macOS 13 | daf8ce5db4390c766da8cd76c5344a8a46d812c35eafd4b567ffed46f43583ec |
LanScreenClient-v2.0.0.zip |
x86_64 | OS X 10.9 | a2bfff466b17558ed292efecf755ff30502c3768de23738082f68a5142db9237 |
The client was built on the iMac itself, because no current toolchain can target 10.9.
v1.3.0 — the link decides the packet size
Jumbo frames that are not there any more
The packet size is a setting in the host but a property of the cable, the adapter and both machines — and a custom MTU does not survive a reboot. So the host stays set to 8900 B while the link quietly goes back to 1500, and nothing complains. An oversized datagram is not rejected; it is split into IP fragments and delivered. Losing any one fragment destroys the whole packet, so a link dropping a fraction of a percent of fragments drops several percent of packets, and the kernel's reassembly queues fill as it goes. It degrades over minutes rather than failing outright, which makes it hard to attribute to the right thing.
On the machine where this was found: 921,196 fragments created from 153,943 datagrams — almost exactly the six-way split an 8900 B payload takes on a 1500 B link — and zero while idle.
The host now asks the interface that routes to the client what it can carry and uses the smaller of that and the configured size. Your setting is left alone, so restoring MTU 9000 on both ends brings jumbo frames back on its own. Check your own link with:
netstat -s -p ip | grep -i fragment
HiDPI is gone
It never worked. Reading back the mode the window server actually settles on gives 1920x1080 points / 1920x1080 pixels whether the toggle was on or off, and three descriptor variants all produced that same 1:1 result. It also made the picture come apart, and it would have bought nothing on a 1080p 2010 panel even if it had worked.
The host now keeps a record
A line a second of throughput, latency, loss and keyframe counts goes to the system log, because nothing kept a history and by the time you notice a degradation the numbers that would explain it are gone.
log show --predicate 'subsystem == "com.lanscreen.host"' --last 15m
Also
Tests/run_mtu_test.shcovers the link-MTU lookup and the payload clamp.- Prebuilt apps are attached here rather than committed to
build/, where they went stale against the source between rebuilds.
Installing
Neither app is notarised.
xattr -dr com.apple.quarantine LanScreenHost.app
On OS X 10.9, right-click the client and choose Open. Screen Recording permission is tied to the code signature, so a downloaded host asks for it separately from one you built yourself.
There is also a greedy branch: a variant tuned harder for responsiveness, with the pointer sent separately from the video. Its client is not protocol-compatible with this one.
v1.2.1 — no more splodge on busy pictures
Fixes blocky corruption on busy pictures — zooming a photo, fast scrolling,
video. The cause was not encoding quality.
The receive buffer was the size of a couple of dozen packets
The client asked the kernel for an 8 MB receive buffer once and gave up if that
failed. The kernel refuses the whole request when it exceeds
kern.ipc.maxsockbuf rather than granting what it can — and OS X 10.9
defaults that ceiling to 4 MB. So on the iMac the call failed every time and
the socket was left at the default receive space: tens of kilobytes.
A 1080p keyframe is around 490 KB, or some 360 packets back to back. Into a
buffer that holds about thirty. Whenever the picture got busy, most of every
frame was dropped.
It now steps down until the kernel grants something, reads back what it
actually got, and reports it in the log and in the statistics overlay (S).
Look for the recv buffer line; under a megabyte is too small.
For the full 8 MB on the iMac:
sudo sysctl -w kern.ipc.maxsockbuf=8388608The encoder was measured and cleared
Before changing anything, encoding a zoom into a detailed image and decoding it
back in process gave 40.5 dB PSNR mean, 35.3 dB worst at 1080p — and that
number did not move for PrioritizeEncodingSpeedOverQuality, the burst cap, or
the keyframe interval. Tests/QualityCheck has the harness.
PrioritizeEncodingSpeedOverQuality is gone from the defaults anyway. It was
added during the latency work, measured as no help, and kept regardless, which
was the wrong call for a flag that trades picture quality for speed.
Also
- A frame that never finished arriving is now noticed. Losing a packet
mid-stream is self-correcting, but losing the tail of the last frame before
the screen goes still left nothing to reveal the gap, so the iMac showed the
previous frame indefinitely. This was the known trade-off listed in v1.2.0. - A transient socket error no longer kills a receive loop. All three used to
end on any unexpected errno, silently, leaving the channel dead for the rest
of the session. Hardening rather than a fix for any confirmed fault.
Downloads
| File | For |
|---|---|
LanScreenHost-v1.2.1-arm64.zip |
The MacBook. arm64, macOS 13+ |
LanScreenClient-v1.2.1-x86_64-osx10.9.zip |
The iMac. x86_64, OS X 10.9+, built on the iMac under Xcode 6.2 |
LanScreen-client-src-v1.2.1.tar.gz |
Client source, to build it yourself |
Neither app is notarised:
xattr -dr com.apple.quarantine LanScreenHost.appOn OS X 10.9, right-click the app and choose Open instead.
Tested on
MacBook Pro (M1 Pro) on macOS 27.0, streaming to an iMac 21.5-inch Mid 2010 on
OS X 10.9.5.
v1.2.0 — a still screen costs nothing
Mostly a load-reduction release. A still screen now costs nothing to stream, and
macOS no longer throttles the host when you stop touching it.
Update both ends. The client and host changed together and older clients do
not work with this host — see Compatibility below.
What got cheaper
| Change | Effect |
|---|---|
| A still screen sends nothing | Was 4.03 Mb/s and a full IDR decode on the 2010 GPU every second, measured at 1920×1080. Now zero |
| Keyframe interval 2s → 5s | Keyframes are several times the size of an inter frame and far more expensive for a 2010 GPU. 2s → 10s measured as 73% less data on a nearly-still screen; 5s is the new default |
| Last frame retained, not copied | Removes a 33 MB/s memcpy on the host |
| Keyframe requests answered 4× faster | The on-demand path ticks every 250 ms instead of every second |
Fixes
- Quality dropped when the cursor stopped moving. The host declared nothing
to the OS, so macOS App Napped it — lower quality of service and coalesced
timers — and moving the cursor was what suppressed that. It now holds
ProcessInfo.beginActivitywhile streaming. Toggle in Power, on by default. - The iMac could revert to a stale frame. The idle keyframe re-encoded a
snapshot copied at most 4× a second, so a screen that changed and then went
still within 250 ms showed the older frame until something moved again. - Wake-on-LAN broadcast never worked. It assumed a /24 and replaced the last
octet, which on a link masked 255.255.0.0 produces an ordinary host address
rather than a broadcast. It now reads the interface's real broadcast address
and netmask from the kernel.
Compatibility
The client used to infer "the host is still there" from video traffic. Since a
still screen no longer sends any, it now takes that from the control channel's
once-a-second ping. An older client against this host blanks every few seconds
on a still screen. Update both.
Known trade-off
If the very last packet of the very last frame before the screen goes still is
lost, the client has no way to notice — there is no following packet to reveal
the sequence gap, and nothing further arrives. The iMac then shows a
one-frame-stale picture until something on screen changes, which repairs it.
Previously the once-a-second keyframe would have repaired this within a second,
at a cost of 4 Mb/s continuously. Loss anywhere else is still detected and
repaired immediately over the control channel. On a direct cable, measured
packet loss across all testing has been zero.
Downloads
| File | For |
|---|---|
LanScreenHost-v1.2.0-arm64.zip |
The MacBook. arm64, macOS 13+ |
LanScreenClient-v1.2.0-x86_64-osx10.9.zip |
The iMac. x86_64, OS X 10.9+, built on the iMac under Xcode 6.2 |
LanScreen-client-src-v1.2.0.tar.gz |
Client source, to build it yourself |
Neither app is notarised:
xattr -dr com.apple.quarantine LanScreenHost.appOn OS X 10.9, right-click the app and choose Open instead.
Tested on
MacBook Pro (M1 Pro) on macOS 27.0, streaming to an iMac 21.5-inch Mid 2010 on
OS X 10.9.5.
v1.1.0 — stays awake, wakes on demand
Screen streaming from an Apple Silicon Mac to a 2010 iMac running OS X 10.9.5,
over a direct Ethernet cable. Hardware H.264 encode, RTP over raw UDP, hardware
decode and zero-copy OpenGL rendering.
This is the first release with power management, and the first with prebuilt
binaries.
Tested on
| Role | Machine | OS |
|---|---|---|
| Host | MacBook Pro (M1 Pro) | macOS 27.0 |
| Client | iMac 21.5-inch, Mid 2010 | OS X 10.9.5, Xcode 6.2 |
Measured on loopback at 1280×720/60: encode through decode runs a median of
14 ms, of which decode is 3 ms. Roughly 30–35 ms glass-to-glass once capture and
render are counted.
New since v1.0.0
- The iMac stays awake while you use it. It sees no keyboard or mouse
activity, so Energy Saver used to blank the display mid-use. The client now
holds a display-sleep assertion while a stream is arriving and releases it the
moment one stops. - The iMac wakes when the MacBook does. Wake-on-LAN, sent on Start and on
system wake, then retried for thirty seconds while the Ethernet link
renegotiates. Requires "Wake for network access" in the iMac's Energy Saver. - They sleep together. The host sends BYE as the MacBook sleeps, so the iMac
is free to sleep too rather than sitting lit up showing a frozen frame.
Downloads
| File | For |
|---|---|
LanScreenHost-v1.1.0-arm64.zip |
The MacBook. arm64, macOS 13+ |
LanScreenClient-v1.1.0-x86_64-osx10.9.zip |
The iMac. x86_64, OS X 10.9+ |
LanScreen-client-src-v1.1.0.tar.gz |
Client source, if you would rather build it yourself |
The client binary was compiled on the iMac itself under Xcode 6.2, because no
current toolchain can target 10.9.
Neither app is notarised, so macOS will quarantine them after download and
refuse to open them:
xattr -dr com.apple.quarantine LanScreenHost.appOn OS X 10.9, right-click the app and choose Open instead.
Screen Recording permission is tied to the code signature, so a downloaded host
asks for it separately from one you built yourself.
Getting started
Set both machines to static IPs on a direct cable (10.0.0.1 and 10.0.0.2,
mask 255.255.255.0), then run the client with -host 10.0.0.1 and press Start
on the MacBook. Full instructions are in the README.
Known limitations
- No audio, no input forwarding, no encryption. IPv4, one client.
- Above 1080p a 2010 GPU falls back to software decode and stutters. On a
27-inch 2010 (2560×1440), stream 1080p and let the iMac scale. - The virtual display uses private CoreGraphics API, which could change in any
macOS release. A hardware HDMI dummy plug is the supported fallback.