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