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