|
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