VERBOSE should be const for better optimization.
[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 const bool VERBOSE = false;
21 #else
22 const 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 bool CreateIOServiceIterator(io_iterator_t* iterator) {
115 CFMutableDictionaryRef matching_dict = IOServiceMatching("AppleIRController");
116 kern_return_t kr = IOServiceGetMatchingServices(
117 kIOMasterPortDefault, matching_dict, iterator);
118 if (kr != KERN_SUCCESS) {
119 ERROR("Failed to IOServiceGetMatchingServices: 0x%x", kr);
120 return false;
121 }
122 return true;;
123 }
124
125 int HandleRead() {
126 CFTypeRef user_prop = CFPreferencesCopyValue(kPrefEnabled, kPrefDomain,
127 kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
128 printf("Userspace property value: %s\n", GetBooleanDescription(user_prop));
129
130 io_iterator_t iterator;
131 if (!CreateIOServiceIterator(&iterator))
132 return EXIT_FAILURE;
133
134 io_object_t service;
135 bool did_find = false;
136 while ((service = IOIteratorNext(iterator))) {
137 did_find = true;
138
139 io_name_t name;
140 kern_return_t kr = IORegistryEntryGetName(service, name);
141 if (kr != KERN_SUCCESS) {
142 ERROR("Failed to IORegistryEntryGetName: 0x%x", kr);
143 continue;
144 }
145
146 LOG("Found AppleIRController: %s", name);
147
148 ScopedCFTypeRef<CFTypeRef> device_enabled(
149 IORegistryEntryCreateCFProperty(service, kPrefEnabled, nullptr, 0));
150 printf("Kernel property value %s: %s\n",
151 name, GetBooleanDescription(device_enabled.get()));
152 }
153
154 if (!did_find) {
155 ERROR("Failed to match any AppleIRController");
156 return EXIT_FAILURE;
157 }
158
159 return EXIT_SUCCESS;
160 }
161
162 int HandleWrite(bool enable) {
163 if (geteuid() != 0) {
164 ERROR("This operation must be performed as root");
165 return EXIT_FAILURE;
166 }
167
168 const CFBooleanRef enabled_value = enable ? kCFBooleanTrue : kCFBooleanFalse;
169
170 CFPreferencesSetValue(kPrefEnabled, enabled_value, kPrefDomain,
171 kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
172 if (!SynchronizePrefs())
173 return EXIT_FAILURE;
174
175 io_iterator_t iterator;
176 if (!CreateIOServiceIterator(&iterator))
177 return EXIT_FAILURE;
178
179 io_object_t service;
180 while ((service = IOIteratorNext(iterator))) {
181 io_name_t name;
182 kern_return_t kr = IORegistryEntryGetName(service, name);
183 if (kr != KERN_SUCCESS) {
184 ERROR("Failed to IORegistryEntryGetName: 0x%x", kr);
185 continue;
186 }
187
188 LOG("Setting property for %s to %d", name, enable);
189
190 kr = IORegistryEntrySetCFProperty(service, kPrefEnabled, enabled_value);
191 if (kr != KERN_SUCCESS) {
192 ERROR("Failed to IORegistryEntrySetCFProperty: 0x%x", kr);
193 continue;
194 }
195 }
196
197 return HandleRead();
198 }
199
200 int main(int argc, char* argv[]) {
201 if (!IsIRAvailable()) {
202 ERROR("No HIDRemoteControl available");
203 return EXIT_FAILURE;
204 }
205
206 if (argc == 1) {
207 return HandleRead();
208 } else if (argc == 2) {
209 if (strcmp("on", argv[1]) == 0) {
210 return HandleWrite(true);
211 } else if (strcmp("off", argv[1]) == 0) {
212 return HandleWrite(false);
213 }
214 }
215
216 fprintf(stderr, "Usage: %s [on|off]\n", argv[0]);
217 return EXIT_FAILURE;
218 }