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