Skip to content

Commit b8052aa

Browse files
committed
Actual initial commit
1 parent f2d0189 commit b8052aa

7 files changed

Lines changed: 264 additions & 1 deletion

File tree

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "cmdf2/lib/Karabiner-VirtualHIDDevice"]
2+
path = cmdf2/lib/Karabiner-VirtualHIDDevice
3+
url = https://github.com/pqrs-org/Karabiner-VirtualHIDDevice-archived

README.md

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,80 @@
11
# TargetDisplayModeTools
2-
Tools and notes on Apple's Target Display Mode for 2009-2011 era iMacs
2+
This is a repo with some tools and notes on Apple's Target Display Mode for 2009-2011-era iMacs.
3+
A few years ago during COVID lockdown I purchased a broken 27" 2011 iMac to use as a cheap external monitor and made a few notes along the way. Key discoveries:
4+
- Newer Macs can use these iMacs as external displays when connected via appropriate Thunderbolt cables/adapters
5+
- Target Display Mode works on macOS 10.13 High Sierra (the latest supported by these iMacs) by copying the binary `/usr/libexec/dpd` from a 10.9 Mavericks installer.
6+
- Target Display Mode is handled by the motherboard and doesn't need a working GPU (handy since failure of the Radeon 6xx0M series GPUs in these iMacs is common)
7+
- Target Display Mode can be activated over SSH using the Karabiner VirtualHIDDevice kext (`cmdf2` code in this repo) so no keyboard or mouse is needed
8+
Hopefully sharing these notes will keep a few more 27" iMacs from being thrown out.
9+
10+
## Repo contents
11+
- `cmdf2` simulates connecting a physical keyboard and pressing Cmd-F2 to activate Target Display Mode. It works over SSH and even without console login (IIRC. Otherwise create a regular user and enable auto-login). It uses Karabiner's VirtualHIDDevice kext and is based on [this example code](https://github.com/pqrs-org/Karabiner-VirtualHIDDevice-archived/tree/master/example/virtual_keyboard_example). You'll need Xcode Command Line Tools to compile.
12+
- `tdm.fish` contains a fish shell function for controlling TDM over SSH and e.g. demonstrates use of `cmdf2` via SSH.
13+
- `com.local.LimitHDDFan.plist` is a `launchd` job file to limit fan speed on startup using smcFanControl.
14+
15+
## Procedure
16+
- Install fresh macOS 10.13 High Sierra from USB
17+
- If GPU is faulty, move graphics kexts out of `/System/Library/Extensions` and rebuild kext cache (more details below). Probably need to disable SIP. Graphics and VNC screen sharing will be slow.
18+
- Connect to network and install updates, enable SSH and VNC access for admin user
19+
- Replace `/usr/libexec/dpd` with version from latest Mavericks installer (more details below)
20+
- If hard drive is missing, install smcFanControl and add launchd job to limit fan speed (more details below)
21+
- Success!
22+
23+
## Problems and solutions
24+
25+
### Booting macOS when the GPU is faulty
26+
*Problem*: 2011 27” iMac boots to grey screen after progress bar completes
27+
*Cause*: faulty AMD Radeon card, common issue with machines of this era
28+
*Workaround*: Move `ATIRadeonX3000.kext` and `ATI6000Controller.kext` (AMD… in later MacOS) from `/System/Library/Extensions` to e.g. `/System/Library/DisabledExtensions`. In later versions of macOS with a kext cache run `touch /System/Library/Extensions` to force a cache rebuild.
29+
- Some sites suggest moving all ATI* or AMD* kexts but this doesn’t appear to be necessary
30+
- Some sites suggest changing nvram `boot-args` to `agc=0` or `agc=-1` but this also doesn’t appear to do anything on an iMac (might be relevant to MBP graphics switching)
31+
32+
*Problem*: Can’t move kexts while SIP is enabled, can’t disable SIP since Recovery Partition is inaccessible due to attempting to load above kexts
33+
*Solution A*: Boot into single-user recovery mode by holding Option, then pressing Cmd-S immediately after selecting the recovery partition. Couldn’t get this working with the 10.13.6 recovery partition, so may need to use an older recovery partition (e.g. 10.11.6). Run `csrutil disable` from single-user-recovery mode. See also <https://apple.stackexchange.com/questions/332587/single-user-recovery-mode-on-high-sierra-10-13-6>
34+
*Solution B*: Plug boot media into another Mac and move kexts from there (or maybe Target Disk Mode)
35+
*Possible solution*: (not tried) disable SIP directly using the nvram command from single-user mode using an older pre-SIP macOS (e.g. 10.6.8): `nvram csr-active-config="w%00%00%00"`
36+
37+
### Getting Target Display Mode to work on macOS 10.13 High Sierra
38+
*Problem*: Target Display Mode will not start. Console contains repeated messages every 10 seconds:
39+
`com.apple.xpc.launchd[1] (com.apple.dpd[182]): Service exited with abnormal code: 75`
40+
*Cause*: Target Display Mode is toggled by `com.apple.dpd` (a single binary located at `/usr/libexec/dpd`) which listens for the Cmd-F2 sequence. dpd is repeatedly crashing which means TDM cannot be triggered.
41+
*Solution*: The issue seems to just affect `dpd` binaries from 10.10 Yosemite onwards (tested on 10.10.5, 10.11.6, 10.13.6 `dpd`). Replacing the later `dpd` binary with the 10.9.5 Mavericks `dpd` (MD5: `6355f7378348de906ad1ace46c936c8c`) resolves the issue. This can be directly extracted by mounting the Mavericks installer PKG with `hdiutil attach` and opening `/Packages/BSD.pkg` in e.g. Suspicious Package.
42+
43+
### Triggering Target Display Mode without a physical keyboard or mouse
44+
*Problem*: Want to remotely toggle Target Display Mode over SSH.
45+
*Solution*: Install [Karabiner Elements](https://karabiner-elements.pqrs.org), which includes a compiled and signed kext for [Karabiner-VirtualHIDDevice](https://github.com/pqrs-org/Karabiner-VirtualHIDDevice-archived). Compile `cmdf2` code in this repo (based on `Karabiner-VirtualHIDDevice/example/virtual_keyboard_example`) and run as sudo over SSH to trigger TDM.
46+
Use `modifier::left_command` followed by `kHIDUsage_Csmr_DisplayBrightnessIncrement`.
47+
`AppleVendorKeyboard_Brightness_Up` doesn’t work for some reason.
48+
*Other useful references*:
49+
- <https://github.com/pqrs-org/Karabiner-VirtualHIDDevice-archived/blob/master/src/include/karabiner_virtual_hid_device.hpp.tmpl>
50+
<https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-700/IOHIDFamily/AppleHIDUsageTables.h>
51+
*Possible solution*: Remap some keys using Karabiner as per
52+
<https://apple.stackexchange.com/questions/170175/switch-to-target-display-mode-without-keyboard>
53+
*Possible solution*: Remap keys with `hidutil` and `launchd` as per <https://rakhesh.com/mac/using-hidutil-to-map-macos-keyboard-keys/> and <https://hidutil-generator.netlify.app>
54+
*Almost solution*: `osascript` over SSH works on macOS 10.6.8 (apparently up to 10.9.5 according to above link): `osascript -e 'tell application "System Events" to key code 144 using command down'`. Confirmed not working on 10.13.6 despite enabling Accessibility access for Terminal and Script Editor. <https://apple.stackexchange.com/questions/362787/automator-and-or-applescript-to-automatically-command-f2-to-enter-target-display>
55+
*Almost solution*: [VirtualKVM](https://github.com/duanefields/VirtualKVM) seems like it might work for those that want TDM triggered upon attach/detach of Thunderbolt between their MacBook and iMac (along with simultaneous enable/disable of Bluetooth to facilitate transfer of mouse/keyboard control) and don’t mind a helper app running on both devices. However, couldn’t get it to work, possibly due to network issues blocking client-host connection or possibly due to daisy-chaining my iMac off an Apple Thunderbolt Display (MBP connects to TBD).
56+
*Almost solution*: <https://github.com/BlueM/cliclick> - didn’t work over SSH, tried Cmd with F2, brightness, etc.
57+
58+
### Reducing fan speed when iMac hard drive is removed
59+
*Problem*: iMac HDD fan runs loudly all the time with no hard drive
60+
*Cause*: Apple hard drives contain a built-in temperature sensor which is now absent, so the SMC runs the fan at maximum to be safe.
61+
*Solution*: Install [smcFanControl](https://www.eidac.de). Set the maximum HDD fan speed using the `smc` tool within using a `launchd` script (here as `com.local.LimitHDDFan.plist`).
62+
*Command*: `/Applications/smcFanControl.app/Contents/Resources/smc -k F1Mx -w 1130`
63+
`smc` tool info: <https://discussions.apple.com/thread/2554924>
64+
launchd template: <https://superuser.com/questions/229773/run-command-on-startup-login-mac-os-x>
65+
Note: use RunAtLoad instead of LaunchOnlyOnce
66+
67+
Problem: Want to track iMac temperatures over SSH to avoid overheating with the fan turned down
68+
Solution: Install [HardwareMonitor](https://www.bresink.com/osx/HardwareMonitor.html) and use command-line tool: `/Applications/HardwareMonitor.app/Contents/MacOS/hwmonitor -c`
69+
70+
### Miscellaneous notes
71+
- `dpd` has an associated binary `dpaudiothru` responsible for passing through audio which seems to work fine on newer macOS versions.
72+
- macOS Mavericks installer can be downloaded from the Mac App Store using `mas-cli` with `mas install 675248567` as long as it has already been purchased, as per <https://forums.macrumors.com/threads/can-someone-give-me-a-mavericks-download-link.2279301/>
73+
- `dmtest` reinstalls missing recovery partitions: `./dmtest ensureRecoveryPartition` <https://davidjb.com/blog/2016/12/creating-a-macos-recovery-partition-without-reinstalling-osx-or-re-running-your-installer/>
74+
- To stop Screen Sharing locking the screen when disconnecting: `sudo defaults write /Library/Preferences/com.apple.RemoteManagement RestoreMachineState -bool NO`
75+
- SilentKnight checks EFI is up-to-date and can install updates for e.g. Gatekeeper and XProtect <https://eclecticlight.co/lockrattler-systhist/>
76+
- Command-line version `silnite` requires Swift Runtime for Command Line Tools: <https://support.apple.com/kb/dl1998?locale=en_US>
77+
- 2011 iMac graphics card upgrade megathread: <https://forums.macrumors.com/threads/2011-imac-graphics-card-upgrade.1596614/>
78+
- Some macOS install packages combine the installer PKG file and the InstallESD DMG and can be mounted directly using `hdiutil attach`
79+
- Older macOS (e.g. 10.6.8) don’t support ed25519 SSH keys
80+

cmdf2/Makefile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CXXFLAGS = -Ilib/Karabiner-VirtualHIDDevice/dist/include -std=c++14 -Wall -Werror -O2
2+
3+
all: main.o
4+
c++ -framework IOKit main.o -o cmdf2
5+
6+
install:
7+
cp cmdf2 /usr/local/bin/cmdf2
8+
9+
clean:
10+
rm -f *.o
11+
rm -f cmdf2
12+
13+
run: all
14+
sudo ./cmdf2

cmdf2/main.cpp

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#include "karabiner_virtual_hid_device_methods.hpp"
2+
#include <CoreFoundation/CoreFoundation.h>
3+
#include <IOKit/IOKitLib.h>
4+
#include <IOKit/hid/IOHIDDevice.h>
5+
#include <IOKit/hid/IOHIDElement.h>
6+
#include <IOKit/hid/IOHIDManager.h>
7+
#include <IOKit/hid/IOHIDQueue.h>
8+
#include <IOKit/hid/IOHIDValue.h>
9+
#include <IOKit/hidsystem/IOHIDShared.h>
10+
#include <IOKit/hidsystem/ev_keymap.h>
11+
#include <cmath>
12+
#include <iostream>
13+
#include <thread>
14+
15+
int main(int argc, const char* argv[]) {
16+
if (getuid() != 0) {
17+
std::cerr << "dispatch_keyboard_event_example requires root privilege." << std::endl;
18+
}
19+
20+
//std::cout << pqrs::karabiner_virtual_hid_device::get_kernel_extension_name() << std::endl;
21+
22+
kern_return_t kr;
23+
io_connect_t connect = IO_OBJECT_NULL;
24+
auto service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceNameMatching(pqrs::karabiner_virtual_hid_device::get_virtual_hid_root_name()));
25+
if (!service) {
26+
std::cerr << "IOServiceGetMatchingService error" << std::endl;
27+
goto finish;
28+
}
29+
30+
kr = IOServiceOpen(service, mach_task_self(), kIOHIDServerConnectType, &connect);
31+
if (kr != KERN_SUCCESS) {
32+
std::cerr << "IOServiceOpen error" << std::endl;
33+
goto finish;
34+
}
35+
36+
{
37+
pqrs::karabiner_virtual_hid_device::properties::keyboard_initialization properties;
38+
kr = pqrs::karabiner_virtual_hid_device_methods::initialize_virtual_hid_keyboard(connect, properties);
39+
if (kr != KERN_SUCCESS) {
40+
std::cerr << "initialize_virtual_hid_keyboard error" << std::endl;
41+
}
42+
43+
while (true) {
44+
//std::cout << "Checking virtual_hid_keyboard is ready..." << std::endl;
45+
46+
bool ready;
47+
kr = pqrs::karabiner_virtual_hid_device_methods::is_virtual_hid_keyboard_ready(connect, ready);
48+
if (kr != KERN_SUCCESS) {
49+
std::cerr << "is_virtual_hid_keyboard_ready error: " << kr << std::endl;
50+
} else {
51+
if (ready) {
52+
//std::cout << "virtual_hid_keyboard is ready." << std::endl;
53+
break;
54+
}
55+
}
56+
57+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
58+
}
59+
}
60+
61+
{
62+
pqrs::karabiner_virtual_hid_device::properties::keyboard_initialization properties;
63+
//properties.country_code = 33;
64+
kr = pqrs::karabiner_virtual_hid_device_methods::initialize_virtual_hid_keyboard(connect, properties);
65+
if (kr != KERN_SUCCESS) {
66+
std::cerr << "initialize_virtual_hid_keyboard error" << std::endl;
67+
}
68+
std::this_thread::sleep_for(std::chrono::milliseconds(500));
69+
}
70+
71+
// left_command key down
72+
{
73+
pqrs::karabiner_virtual_hid_device::hid_report::keyboard_input report;
74+
report.modifiers.insert(pqrs::karabiner_virtual_hid_device::hid_report::modifier::left_command);
75+
76+
kr = pqrs::karabiner_virtual_hid_device_methods::post_keyboard_input_report(connect, report);
77+
if (kr != KERN_SUCCESS) {
78+
std::cerr << "post_keyboard_input_report error" << std::endl;
79+
}
80+
}
81+
82+
// brightness_up (f2) key down
83+
{
84+
// Apple brightness key doesn't work for enabling TDM
85+
//pqrs::karabiner_virtual_hid_device::hid_report::apple_vendor_keyboard_input report;
86+
//report.keys.insert(0x0020); // kHIDUsage_AppleVendorKeyboard_Brightness_Up
87+
88+
// F2 key doesn't work for enabling TDM
89+
//pqrs::karabiner_virtual_hid_device::hid_report::keyboard_input report;
90+
//report.keys.insert(kHIDUsage_KeyboardF2);
91+
92+
// Consumer display brightness increment does...?
93+
pqrs::karabiner_virtual_hid_device::hid_report::consumer_input report;
94+
report.keys.insert(kHIDUsage_Csmr_DisplayBrightnessIncrement);
95+
96+
kr = pqrs::karabiner_virtual_hid_device_methods::post_keyboard_input_report(connect, report);
97+
if (kr != KERN_SUCCESS) {
98+
std::cerr << "post_keyboard_input_report error" << std::endl;
99+
}
100+
}
101+
102+
// keyboard reset
103+
kr = pqrs::karabiner_virtual_hid_device_methods::reset_virtual_hid_keyboard(connect);
104+
if (kr != KERN_SUCCESS) {
105+
std::cerr << "reset_virtual_hid_keyboard error" << std::endl;
106+
}
107+
108+
finish:
109+
if (connect) {
110+
IOServiceClose(connect);
111+
}
112+
if (service) {
113+
IOObjectRelease(service);
114+
}
115+
116+
return 0;
117+
}

com.local.LimitHDDFan.plist

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>Label</key>
6+
<string>com.local.LimitHDDFan</string>
7+
<key>UserName</key>
8+
<string>root</string>
9+
<key>ProgramArguments</key>
10+
<array>
11+
<string>/Applications/smcFanControl.app/Contents/Resources/smc</string>
12+
<string>-k</string>
13+
<string>F1Mx</string>
14+
<string>-w</string>
15+
<string>1130</string>
16+
</array>
17+
<key>OnDemand</key>
18+
<false/>
19+
<key>RunAtLoad</key>
20+
<true/>
21+
</dict>
22+
</plist>

tdm.fish

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
function tdm --argument-names cmd --description "Shortcuts for Target Display Mode"
2+
switch "$cmd"
3+
case cmdf2 toggle tog
4+
ssh tdm.local "sudo cmdf2"
5+
case goodnight
6+
ssh tdm.local 'bash -c "sudo cmdf2; sleep 10; sudo shutdown -h now" </dev/null >/dev/null 2>/dev/null &'
7+
case fans
8+
ssh tdm.local /Applications/smcFanControl.app/Contents/Resources/smc -f
9+
case temps
10+
# https://apple.stackexchange.com/questions/54329/can-i-get-the-cpu-temperature-and-fan-speed-from-the-command-line-in-os-x
11+
ssh tdm.local /Applications/HardwareMonitor.app/Contents/MacOS/hwmonitor -c
12+
case hwinfo
13+
ssh tdm.local system_profiler SPHardwareDataType
14+
case tbinfo
15+
ssh tdm.local system_profiler SPThunderboltDataType
16+
case shutdown
17+
ssh tdm.local sudo /sbin/shutdown -h now
18+
case reboot
19+
ssh tdm.local sudo /sbin/shutdown -r now
20+
case ssh
21+
ssh tdm.local
22+
case screenshare
23+
#ssh -xfNMS "$HOME/.ssh/tdm-fwd" -L 5901:localhost:5900 tdm.local
24+
open vnc://tdm._rfb._tcp.local
25+
case \*
26+
echo "Usage: tdm {cmdf2|goodnight|fans|temps|hwinfo|tbinfo|shutdown|reboot|wake|ssh|screenshare}"
27+
end
28+
end

0 commit comments

Comments
 (0)