|
1 | 1 | /** |
2 | 2 | * PS Move API - An interface for the PS Move Motion Controller |
3 | | - * Copyright (c) 2016, 2023 Thomas Perl <[email protected]> |
| 3 | + * Copyright (c) 2016, 2023, 2025 Thomas Perl <[email protected]> |
4 | 4 | * All rights reserved. |
5 | 5 | * |
6 | 6 | * Redistribution and use in source and binary forms, with or without |
|
57 | 57 |
|
58 | 58 | #define OSXPAIR_DEBUG(...) PSMOVE_INFO(__VA_ARGS__) |
59 | 59 |
|
| 60 | +namespace { |
| 61 | + |
60 | 62 | struct ScopedNSAutoreleasePool { |
61 | 63 | ScopedNSAutoreleasePool() |
62 | 64 | : pool([[NSAutoreleasePool alloc] init]) |
|
68 | 70 | } |
69 | 71 |
|
70 | 72 | private: |
| 73 | + ScopedNSAutoreleasePool(const ScopedNSAutoreleasePool &) = delete; |
| 74 | + ScopedNSAutoreleasePool &operator=(const ScopedNSAutoreleasePool &) = delete; |
| 75 | + |
71 | 76 | NSAutoreleasePool *pool; |
72 | 77 | }; |
73 | 78 |
|
| 79 | +std::vector<std::string> |
| 80 | +subprocess(const std::string &cmdline) |
| 81 | +{ |
| 82 | + FILE *fp = popen(cmdline.c_str(), "r"); |
| 83 | + if (!fp) { |
| 84 | + return {}; |
| 85 | + } |
| 86 | + |
| 87 | + std::vector<std::string> result; |
| 88 | + char line[1024]; |
| 89 | + while (fgets(line, sizeof(line), fp)) { |
| 90 | + // Remove trailing newline |
| 91 | + line[strlen(line)-1] = '\0'; |
| 92 | + result.emplace_back(line); |
| 93 | + } |
| 94 | + |
| 95 | + pclose(fp); |
| 96 | + return result; |
| 97 | +} |
| 98 | + |
74 | 99 | static int |
75 | 100 | macosx_bluetooth_set_powered(int powered) |
76 | 101 | { |
|
95 | 120 | return 1; |
96 | 121 | } |
97 | 122 |
|
98 | | -static char * |
99 | | -macosx_get_btaddr() |
100 | | -{ |
101 | | - ScopedNSAutoreleasePool pool; |
102 | | - |
103 | | - char *result; |
104 | | - |
105 | | - macosx_bluetooth_set_powered(1); |
106 | | - |
107 | | - IOBluetoothHostController *controller = |
108 | | - [IOBluetoothHostController defaultController]; |
109 | | - |
110 | | - NSString *addr = [controller addressAsString]; |
111 | | - psmove_return_val_if_fail(addr != NULL, NULL); |
112 | | - |
113 | | - result = strdup([addr UTF8String]); |
114 | | - psmove_return_val_if_fail(result != NULL, NULL); |
115 | | - |
116 | | - char *tmp = result; |
117 | | - while (*tmp) { |
118 | | - if (*tmp == '-') { |
119 | | - *tmp = ':'; |
120 | | - } |
121 | | - |
122 | | - tmp++; |
123 | | - } |
124 | | - |
125 | | - return result; |
126 | | -} |
127 | | - |
128 | | -static int |
| 123 | +static bool |
129 | 124 | macosx_blued_running() |
130 | 125 | { |
131 | | - FILE *fp = popen("ps -axo comm", "r"); |
132 | | - char command[1024]; |
133 | | - int running = 0; |
134 | | - |
135 | | - while (fgets(command, sizeof(command), fp)) { |
136 | | - /* Remove trailing newline */ |
137 | | - command[strlen(command)-1] = '\0'; |
138 | | - |
139 | | - if (strcmp(command, "/usr/sbin/blued") == 0) { |
140 | | - running = 1; |
| 126 | + for (const auto &line: subprocess("/bin/ps -axo comm")) { |
| 127 | + if (line == "/usr/sbin/blued") { |
| 128 | + return true; |
141 | 129 | } |
142 | 130 | } |
143 | 131 |
|
144 | | - pclose(fp); |
145 | | - |
146 | | - return running; |
| 132 | + return false; |
147 | 133 | } |
148 | 134 |
|
149 | 135 | static bool |
150 | 136 | macosx_blued_is_paired(const std::string &btaddr) |
151 | 137 | { |
152 | | - FILE *fp = popen("defaults read " OSX_BT_CONFIG_PATH " HIDDevices", "r"); |
153 | | - char line[1024]; |
154 | | - int found = 0; |
155 | | - |
156 | 138 | /** |
157 | 139 | * Example output that we need to parse: |
158 | 140 | * |
|
164 | 146 | * |
165 | 147 | **/ |
166 | 148 |
|
167 | | - while (fgets(line, sizeof(line), fp)) { |
168 | | - char *entry = strchr(line, '"'); |
169 | | - if (entry) { |
170 | | - entry++; |
171 | | - char *delim = strchr(entry, '"'); |
172 | | - if (delim) { |
173 | | - *delim = '\0'; |
174 | | - if (strcmp(entry, btaddr.c_str()) == 0) { |
175 | | - found = 1; |
| 149 | + for (const auto &line: subprocess("/usr/bin/defaults read " OSX_BT_CONFIG_PATH " HIDDevices")) { |
| 150 | + size_t pos = line.find('"'); |
| 151 | + if (pos != std::string::npos) { |
| 152 | + ++pos; |
| 153 | + size_t rpos = line.rfind('"'); |
| 154 | + if (rpos != std::string::npos) { |
| 155 | + std::string entry = line.substr(pos, rpos - pos); |
| 156 | + if (entry == btaddr) { |
| 157 | + return true; |
176 | 158 | } |
177 | 159 | } |
178 | 160 | } |
179 | 161 | } |
180 | 162 |
|
181 | | - pclose(fp); |
182 | | - return found; |
| 163 | + return false; |
183 | 164 | } |
184 | 165 |
|
185 | | -struct MacOSVersionNumber { |
186 | | - MacOSVersionNumber(int major=-1, int minor=-1) : major(major), minor(minor) {} |
187 | | - |
188 | | - bool valid() const { return major != -1 && minor != -1; } |
189 | | - |
190 | | - int major; |
191 | | - int minor; |
192 | | -}; |
| 166 | +struct MacOSVersion { |
| 167 | + static MacOSVersion |
| 168 | + running() |
| 169 | + { |
| 170 | + for (const auto &line: subprocess("/usr/bin/sw_vers -productVersion")) { |
| 171 | + int major, minor, patch = 0; |
| 172 | + int assigned = sscanf(line.c_str(), "%d.%d.%d", &major, &minor, &patch); |
| 173 | + |
| 174 | + /** |
| 175 | + * On Mac OS X 10.8.0, the command returns "10.8", so we allow parsing |
| 176 | + * only the first two numbers of the triplet, leaving the patch version |
| 177 | + * to the default (0) set above. |
| 178 | + * |
| 179 | + * See: https://github.com/thp/psmoveapi/issues/32 |
| 180 | + **/ |
| 181 | + if (assigned == 2 || assigned == 3) { |
| 182 | + return MacOSVersion {major, minor}; |
| 183 | + } |
| 184 | + } |
193 | 185 |
|
194 | | -bool |
195 | | -operator<(const MacOSVersionNumber &a, const MacOSVersionNumber &b) |
196 | | -{ |
197 | | - return (a.major <= b.major && a.minor < b.minor); |
198 | | -} |
| 186 | + return MacOSVersion {}; |
| 187 | + } |
199 | 188 |
|
200 | | -bool |
201 | | -operator>=(const MacOSVersionNumber &a, const MacOSVersionNumber &b) |
202 | | -{ |
203 | | - return !(a < b); |
204 | | -} |
| 189 | + explicit MacOSVersion(int major=-1, int minor=-1) : major(major), minor(minor) {} |
205 | 190 |
|
206 | | -static MacOSVersionNumber |
207 | | -macosx_get_major_minor_version() |
208 | | -{ |
209 | | - char tmp[1024]; |
210 | | - int major, minor, patch = 0; |
211 | | - FILE *fp; |
| 191 | + bool operator<(const MacOSVersion &other) const { |
| 192 | + return major < other.major || (major == other.major && minor < other.minor); |
| 193 | + } |
212 | 194 |
|
213 | | - fp = popen("sw_vers -productVersion", "r"); |
214 | | - psmove_return_val_if_fail(fp != NULL, MacOSVersionNumber()); |
215 | | - psmove_return_val_if_fail(fgets(tmp, sizeof(tmp), fp) != NULL, MacOSVersionNumber()); |
216 | | - pclose(fp); |
| 195 | + bool operator>=(const MacOSVersion &other) const { |
| 196 | + return major > other.major || (major == other.major && minor >= other.minor); |
| 197 | + } |
217 | 198 |
|
218 | | - int assigned = sscanf(tmp, "%d.%d.%d", &major, &minor, &patch); |
| 199 | + bool valid() const { return major != -1 && minor != -1; } |
219 | 200 |
|
220 | | - /** |
221 | | - * On Mac OS X 10.8.0, the command returns "10.8", so we allow parsing |
222 | | - * only the first two numbers of the triplet, leaving the patch version |
223 | | - * to the default (0) set above. |
224 | | - * |
225 | | - * See: https://github.com/thp/psmoveapi/issues/32 |
226 | | - **/ |
227 | | - psmove_return_val_if_fail(assigned == 2 || assigned == 3, MacOSVersionNumber()); |
| 201 | + int major; |
| 202 | + int minor; |
| 203 | +}; |
228 | 204 |
|
229 | | - return MacOSVersionNumber(major, minor); |
230 | | -} |
| 205 | +} // end anonymous namespace |
231 | 206 |
|
232 | 207 | bool |
233 | 208 | psmove_port_register_psmove(char *addr, char *host, enum PSMove_Model_Type model) |
234 | 209 | { |
235 | | - bool result = true; |
236 | | - |
237 | 210 | // TODO: Host is ignored for now |
238 | 211 |
|
239 | 212 | // TODO: FIXME: If necessary, handle different controller models differently. |
|
246 | 219 | return false; |
247 | 220 | } |
248 | 221 |
|
249 | | - auto macos_version = macosx_get_major_minor_version(); |
| 222 | + auto macos_version = MacOSVersion::running(); |
250 | 223 | if (!macos_version.valid()) { |
251 | 224 | OSXPAIR_DEBUG("Cannot detect macOS version.\n"); |
252 | 225 | return false; |
253 | | - } else if (macos_version >= MacOSVersionNumber(13, 0)) { |
| 226 | + } else if (macos_version >= MacOSVersion(13, 0)) { |
254 | 227 | PSMOVE_WARNING("Pairing not yet supported on macOS Ventura, see https://github.com/thp/psmoveapi/issues/457"); |
255 | 228 | return false; |
256 | | - } else if (macos_version < MacOSVersionNumber(10, 7)) { |
| 229 | + } else if (macos_version < MacOSVersion(10, 7)) { |
257 | 230 | OSXPAIR_DEBUG("No need to add entry for macOS before 10.7.\n"); |
258 | 231 | return false; |
259 | | - } else { |
260 | | - OSXPAIR_DEBUG("Detected: macOS %d.%d\n", macos_version.major, macos_version.minor); |
261 | 232 | } |
262 | 233 |
|
263 | | - std::string command = format("defaults write %s HIDDevices -array-add %s", |
264 | | - OSX_BT_CONFIG_PATH, btaddr.c_str()); |
| 234 | + OSXPAIR_DEBUG("Detected: macOS %d.%d\n", macos_version.major, macos_version.minor); |
265 | 235 |
|
266 | 236 | if (macosx_blued_is_paired(btaddr)) { |
267 | 237 | OSXPAIR_DEBUG("Entry for %s already present.\n", btaddr.c_str()); |
268 | 238 | return true; |
269 | 239 | } |
270 | 240 |
|
271 | | - if (macos_version < MacOSVersionNumber(10, 10)) |
272 | | - { |
| 241 | + if (macos_version < MacOSVersion(10, 10)) { |
273 | 242 | if (!macosx_bluetooth_set_powered(0)) { |
274 | 243 | OSXPAIR_DEBUG("Cannot shutdown Bluetooth (shut it down manually).\n"); |
275 | 244 | } |
276 | 245 |
|
277 | | - int i = 0; |
278 | 246 | OSXPAIR_DEBUG("Waiting for blued shutdown (takes ca. 42s) ...\n"); |
279 | 247 | while (macosx_blued_running()) { |
280 | 248 | psmove_port_sleep_ms(1000); |
281 | | - i++; |
282 | 249 | } |
283 | 250 | OSXPAIR_DEBUG("blued successfully shutdown.\n"); |
284 | 251 | } |
285 | 252 |
|
| 253 | + std::string command = format("/usr/bin/defaults write %s HIDDevices -array-add %s", |
| 254 | + OSX_BT_CONFIG_PATH, btaddr.c_str()); |
286 | 255 | if (geteuid() != 0) { |
287 | 256 | // Not running using setuid or sudo, must use osascript to gain privileges |
288 | | - command = format("osascript -e 'do shell script \"%s\" with administrator privileges'", |
| 257 | + command = format("/usr/bin/osascript -e 'do shell script \"%s\" with administrator privileges'", |
289 | 258 | command.c_str()); |
290 | 259 | } |
291 | 260 |
|
292 | 261 | OSXPAIR_DEBUG("Running: '%s'\n", command.c_str()); |
| 262 | + bool result = true; |
293 | 263 | if (system(command.c_str()) != 0) { |
294 | 264 | OSXPAIR_DEBUG("Could not run the command."); |
295 | 265 | result = false; |
296 | 266 | } |
297 | 267 |
|
298 | | - if (macos_version < MacOSVersionNumber(10, 10)) |
| 268 | + if (macos_version < MacOSVersion(10, 10)) |
299 | 269 | { |
300 | 270 | // FIXME: In OS X 10.7 this might not work - fork() and call set_powered(1) |
301 | 271 | // from a fresh process (e.g. like "blueutil 1") to switch Bluetooth on |
|
369 | 339 | char * |
370 | 340 | psmove_port_get_host_bluetooth_address() |
371 | 341 | { |
372 | | - return macosx_get_btaddr(); |
| 342 | + ScopedNSAutoreleasePool pool; |
| 343 | + |
| 344 | + macosx_bluetooth_set_powered(1); |
| 345 | + |
| 346 | + IOBluetoothHostController *controller = |
| 347 | + [IOBluetoothHostController defaultController]; |
| 348 | + |
| 349 | + NSString *addr = [controller addressAsString]; |
| 350 | + psmove_return_val_if_fail(addr != NULL, NULL); |
| 351 | + |
| 352 | + char *result = strdup([addr UTF8String]); |
| 353 | + psmove_return_val_if_fail(result != NULL, NULL); |
| 354 | + |
| 355 | + if (_psmove_normalize_btaddr_inplace(result, true, ':') == NULL) { |
| 356 | + free(result); |
| 357 | + return NULL; |
| 358 | + } |
| 359 | + |
| 360 | + return result; |
373 | 361 | } |
0 commit comments