Make sure HandleWrite() is run as root.
[apple-ir-control.git] / apple-ir-control.cc
1 #include <CoreFoundation/CoreFoundation.h>
2 #include <IOKit/hid/IOHIDLib.h>
3 #include <IOKit/IOKitLib.h>
4 #include <unistd.h>
5 #include <vector>
6
7 const CFStringRef kPrefDomain = CFSTR("com.apple.driver.AppleIRController");
8 const CFStringRef kPrefEnabled = CFSTR("DeviceEnabled");
9
10 #if NDEBUG
11 bool VERBOSE = false;
12 #else
13 bool VERBOSE = true;
14 #endif
15
16 #define LOG(msg, ...) do { \
17 if (VERBOSE) { \
18 printf(msg " [%s:%d]\n", ##__VA_ARGS__, __FILE__, __LINE__); \
19 } \
20 } while(0)
21 #define ERROR(msg, ...) fprintf(stderr, msg " [%s:%d]\n", ##__VA_ARGS__, __FILE__, __LINE__)
22
23 class ScopedIOHIDManager {
24 public:
25 ScopedIOHIDManager()
26 : manager_(IOHIDManagerCreate(nullptr, kIOHIDManagerOptionNone)) {}
27
28 ~ScopedIOHIDManager() {
29 IOHIDManagerClose(manager_, kIOHIDManagerOptionNone);
30 }
31
32 ScopedIOHIDManager(const ScopedIOHIDManager&) = delete;
33
34 IOHIDManagerRef get() { return manager_; }
35
36 private:
37 IOHIDManagerRef manager_;
38 };
39
40 template <typename T>
41 class ScopedCFTypeRef {
42 public:
43 ScopedCFTypeRef(T t = nullptr) : t_(t) {}
44 ~ScopedCFTypeRef() {
45 if (t_) {
46 CFRelease(t_);
47 }
48 }
49
50 ScopedCFTypeRef(const ScopedCFTypeRef<T>&) = delete;
51
52 operator bool() { return t_ != nullptr; }
53
54 T get() { return t_; }
55
56 T* pointer_to() { return &t_; }
57
58 private:
59 T t_;
60 };
61
62 bool IsIRAvailable() {
63 ScopedIOHIDManager manager;
64 IOHIDManagerSetDeviceMatching(manager.get(), nullptr);
65 ScopedCFTypeRef<CFSetRef> devices(IOHIDManagerCopyDevices(manager.get()));
66 if (!devices) {
67 ERROR("Failed to IOHIDManagerCopyDevices");
68 return false;
69 }
70 std::vector<void*> devices_array(CFSetGetCount(devices.get()), nullptr);
71 CFSetGetValues(devices.get(), const_cast<const void**>(devices_array.data()));
72 for (const auto& device : devices_array) {
73 CFTypeRef prop =
74 IOHIDDeviceGetProperty(reinterpret_cast<IOHIDDeviceRef>(device),
75 CFSTR("HIDRemoteControl"));
76 if (prop) {
77 if (VERBOSE) {
78 LOG("Located HIDRemoteControl:");
79 CFShow(device);
80 }
81 return true;
82 }
83 }
84 return false;
85 }
86
87 const char* GetBooleanDescription(CFTypeRef boolean) {
88 if (CFGetTypeID(boolean) != CFBooleanGetTypeID()) {
89 ERROR("Unexpected non-boolean CFTypeRef");
90 abort();
91 }
92 return CFBooleanGetValue(static_cast<CFBooleanRef>(boolean)) ? "on" : "off";
93 }
94
95 bool SynchronizePrefs() {
96 bool rv = CFPreferencesSynchronize(kPrefDomain, kCFPreferencesAnyUser,
97 kCFPreferencesCurrentHost);
98 if (!rv) {
99 ERROR("Failed to CFPreferencesSynchronize");
100 }
101 return rv;
102 }
103
104 CFTypeRef GetUserPropertyValue() {
105 return CFPreferencesCopyValue(kPrefEnabled, kPrefDomain,
106 kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
107 }
108
109 io_iterator_t CreateIOServiceIterator() {
110 CFMutableDictionaryRef matching_dict = IOServiceMatching("AppleIRController");
111 io_iterator_t iterator;
112 kern_return_t kr = IOServiceGetMatchingServices(
113 kIOMasterPortDefault, matching_dict, &iterator);
114 if (kr != KERN_SUCCESS) {
115 ERROR("Failed to IOServiceGetMatchingServices: 0x%x", kr);
116 return 0;
117 }
118 return iterator;
119 }
120
121 int HandleRead() {
122 if (!SynchronizePrefs())
123 return EXIT_FAILURE;
124
125 CFTypeRef user_prop = GetUserPropertyValue();
126 printf("Userspace property value: %s\n", GetBooleanDescription(user_prop));
127
128 io_iterator_t iterator = CreateIOServiceIterator();
129 if (!iterator)
130 return EXIT_FAILURE;
131
132 io_object_t service;
133 bool did_find = false;
134 while ((service = IOIteratorNext(iterator))) {
135 did_find = true;
136
137 io_name_t name;
138 kern_return_t kr = IORegistryEntryGetName(service, name);
139 if (kr != KERN_SUCCESS) {
140 ERROR("Failed to IORegistryEntryGetName: 0x%x", kr);
141 continue;
142 }
143
144 LOG("Found AppleIRController: %s", name);
145
146 ScopedCFTypeRef<CFTypeRef> device_enabled(
147 IORegistryEntryCreateCFProperty(service, kPrefEnabled, nullptr, 0));
148 printf("Kernel property value %s: %s\n",
149 name, GetBooleanDescription(device_enabled.get()));
150
151 #if 0
152 ScopedCFTypeRef<CFMutableDictionaryRef> props;
153 kr = IORegistryEntryCreateCFProperties(service, props.pointer_to(), nullptr, 0);
154 if (kr != KERN_SUCCESS) {
155 ERROR("Failed to IORegistryEntryCreateCFProperties(%s) = 0x%x", name, kr);
156 continue;
157 }
158 CFShow(props.get());
159 #endif
160 }
161
162 if (!did_find) {
163 ERROR("Failed to match AppleIRController");
164 return EXIT_FAILURE;
165 }
166
167 return EXIT_SUCCESS;
168 }
169
170 int HandleWrite(bool enable) {
171 if (geteuid() != 0) {
172 ERROR("This operation must be performed as root");
173 return EXIT_FAILURE;
174 }
175
176 const CFBooleanRef enabled_value = enable ? kCFBooleanTrue : kCFBooleanFalse;
177
178 CFPreferencesSetValue(kPrefEnabled, enabled_value, kPrefDomain,
179 kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
180 if (!SynchronizePrefs())
181 return EXIT_FAILURE;
182
183 io_iterator_t iterator = CreateIOServiceIterator();
184 io_object_t service;
185 while ((service = IOIteratorNext(iterator))) {
186 io_name_t name;
187 kern_return_t kr = IORegistryEntryGetName(service, name);
188 if (kr != KERN_SUCCESS) {
189 ERROR("Failed to IORegistryEntryGetName: 0x%x", kr);
190 continue;
191 }
192
193 LOG("Setting property for %s to %d", name, enable);
194
195 kr = IORegistryEntrySetCFProperty(service, kPrefEnabled, enabled_value);
196 if (kr != KERN_SUCCESS) {
197 ERROR("Failed to IORegistryEntrySetCFProperty: 0x%x", kr);
198 continue;
199 }
200 }
201
202 return HandleRead();
203 }
204
205 int main(int argc, char* argv[]) {
206 if (!IsIRAvailable()) {
207 ERROR("No HIDRemoteControl available");
208 return EXIT_FAILURE;
209 }
210
211 if (argc == 1) {
212 return HandleRead();
213 } else if (argc == 2) {
214 if (strcmp("on", argv[1]) == 0) {
215 return HandleWrite(true);
216 } else if (strcmp("off", argv[1]) == 0) {
217 return HandleWrite(false);
218 }
219 }
220
221 fprintf(stderr, "Usage: %s [on|off]\n", argv[0]);
222 return EXIT_FAILURE;
223 }