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