Bug 1646817 - Support DocumentChannel process switching in sidebars and popups r...
[gecko.git] / hal / cocoa / smslib.mm
blob1a27d404a3f137d36a24cc6bc8ce3146e3d2c952
1 /*
2  * smslib.m
3  *
4  * SMSLib Sudden Motion Sensor Access Library
5  * Copyright (c) 2010 Suitable Systems
6  * All rights reserved.
7  *
8  * Developed by: Daniel Griscom
9  *               Suitable Systems
10  *               http://www.suitable.com
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a
13  * copy of this software and associated documentation files (the
14  * "Software"), to deal with the Software without restriction, including
15  * without limitation the rights to use, copy, modify, merge, publish,
16  * distribute, sublicense, and/or sell copies of the Software, and to
17  * permit persons to whom the Software is furnished to do so, subject to
18  * the following conditions:
19  *
20  * - Redistributions of source code must retain the above copyright notice,
21  * this list of conditions and the following disclaimers.
22  *
23  * - Redistributions in binary form must reproduce the above copyright
24  * notice, this list of conditions and the following disclaimers in the
25  * documentation and/or other materials provided with the distribution.
26  *
27  * - Neither the names of Suitable Systems nor the names of its
28  * contributors may be used to endorse or promote products derived from
29  * this Software without specific prior written permission.
30  *
31  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
32  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
34  * IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
35  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
36  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
37  * SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
38  *
39  * For more information about SMSLib, see
40  *              <http://www.suitable.com/tools/smslib.html>
41  * or contact
42  *              Daniel Griscom
43  *              Suitable Systems
44  *              1 Centre Street, Suite 204
45  *              Wakefield, MA 01880
46  *              (781) 665-0053
47  *
48  */
50 #import <IOKit/IOKitLib.h>
51 #import <sys/sysctl.h>
52 #import <math.h>
53 #import "smslib.h"
55 #pragma mark Internal structures
57 // Represents a single axis of a type of sensor.
58 typedef struct axisStruct {
59   int enabled;  // Non-zero if axis is valid in this sensor
60   int index;    // Location in struct of first byte
61   int size;     // Number of bytes
62   float zerog;  // Value meaning "zero g"
63   float oneg;   // Change in value meaning "increase of one g"
64                 // (can be negative if axis sensor reversed)
65 } axisStruct;
67 // Represents the configuration of a type of sensor.
68 typedef struct sensorSpec {
69   const char* model;      // Prefix of model to be tested
70   const char* name;       // Name of device to be read
71   unsigned int function;  // Kernel function index
72   int recordSize;         // Size of record to be sent/received
73   axisStruct axes[3];     // Description of three axes (X, Y, Z)
74 } sensorSpec;
76 // Configuration of all known types of sensors. The configurations are
77 // tried in order until one succeeds in returning data.
78 // All default values are set here, but each axis' zerog and oneg values
79 // may be changed to saved (calibrated) values.
81 // These values came from SeisMaCalibrate calibration reports. In general I've
82 // found the following:
83 //      - All Intel-based SMSs have 250 counts per g, centered on 0, but the signs
84 //              are different (and in one case two axes are swapped)
85 //      - PowerBooks and iBooks all have sensors centered on 0, and reading
86 //              50-53 steps per gravity (but with differing polarities!)
87 //      - PowerBooks and iBooks of the same model all have the same axis polarities
88 //      - PowerBook and iBook access methods are model- and OS version-specific
90 // So, the sequence of tests is:
91 //      - Try model-specific access methods. Note that the test is for a match to the
92 //              beginning of the model name, e.g. the record with model name "MacBook"
93 //              matches computer models "MacBookPro1,2" and "MacBook1,1" (and ""
94 //              matches any model).
95 //      - If no model-specific record's access fails, then try each model-independent
96 //              access method in order, stopping when one works.
97 static const sensorSpec sensors[] = {
98     // ****** Model-dependent methods ******
99     // The PowerBook5,6 is one of the G4 models that seems to lose
100     // SMS access until the next reboot.
101     {"PowerBook5,6",
102      "IOI2CMotionSensor",
103      21,
104      60,
105      {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, -51.5}, {1, 2, 1, 0, -51.5}}},
106     // The PowerBook5,7 is one of the G4 models that seems to lose
107     // SMS access until the next reboot.
108     {"PowerBook5,7",
109      "IOI2CMotionSensor",
110      21,
111      60,
112      {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}},
113     // Access seems to be reliable on the PowerBook5,8
114     {"PowerBook5,8",
115      "PMUMotionSensor",
116      21,
117      60,
118      {{1, 0, 1, 0, -51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, -51.5}}},
119     // Access seems to be reliable on the PowerBook5,9
120     {"PowerBook5,9",
121      "PMUMotionSensor",
122      21,
123      60,
124      {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, -51.5}, {1, 2, 1, 0, -51.5}}},
125     // The PowerBook6,7 is one of the G4 models that seems to lose
126     // SMS access until the next reboot.
127     {"PowerBook6,7",
128      "IOI2CMotionSensor",
129      21,
130      60,
131      {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}},
132     // The PowerBook6,8 is one of the G4 models that seems to lose
133     // SMS access until the next reboot.
134     {"PowerBook6,8",
135      "IOI2CMotionSensor",
136      21,
137      60,
138      {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}},
139     // MacBook Pro Core 2 Duo 17". Note the reversed Y and Z axes.
140     {"MacBookPro2,1",
141      "SMCMotionSensor",
142      5,
143      40,
144      {{1, 0, 2, 0, 251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, -251}}},
145     // MacBook Pro Core 2 Duo 15" AND 17" with LED backlight, introduced June '07.
146     // NOTE! The 17" machines have the signs of their X and Y axes reversed
147     // from this calibration, but there's no clear way to discriminate between
148     // the two machines.
149     {"MacBookPro3,1",
150      "SMCMotionSensor",
151      5,
152      40,
153      {{1, 0, 2, 0, -251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, -251}}},
154     // ... specs?
155     {"MacBook5,2",
156      "SMCMotionSensor",
157      5,
158      40,
159      {{1, 0, 2, 0, -251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, -251}}},
160     // ... specs?
161     {"MacBookPro5,1",
162      "SMCMotionSensor",
163      5,
164      40,
165      {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}},
166     // ... specs?
167     {"MacBookPro5,2",
168      "SMCMotionSensor",
169      5,
170      40,
171      {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}},
172     // This is speculative, based on a single user's report. Looks like the X and Y axes
173     // are swapped. This is true for no other known Appple laptop.
174     {"MacBookPro5,3",
175      "SMCMotionSensor",
176      5,
177      40,
178      {{1, 2, 2, 0, -251}, {1, 0, 2, 0, -251}, {1, 4, 2, 0, -251}}},
179     // ... specs?
180     {"MacBookPro5,4",
181      "SMCMotionSensor",
182      5,
183      40,
184      {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}},
185     // ****** Model-independent methods ******
186     // Seen once with PowerBook6,8 under system 10.3.9; I suspect
187     // other G4-based 10.3.* systems might use this
188     {"", "IOI2CMotionSensor", 24, 60, {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}},
189     // PowerBook5,6 , PowerBook5,7 , PowerBook6,7 , PowerBook6,8
190     // under OS X 10.4.*
191     {"", "IOI2CMotionSensor", 21, 60, {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}},
192     // PowerBook5,8 , PowerBook5,9 under OS X 10.4.*
193     {"",
194      "PMUMotionSensor",
195      21,
196      60,
197      {// Each has two out of three gains negative, but it's different
198       // for the different models. So, this will be right in two out
199       // of three axis for either model.
200       {1, 0, 1, 0, -51.5},
201       {1, 1, 1, -6, -51.5},
202       {1, 2, 1, 0, -51.5}}},
203     // All MacBook, MacBookPro models. Hardware (at least on early MacBookPro 15")
204     // is Kionix KXM52-1050 three-axis accelerometer chip. Data is at
205     // http://kionix.com/Product-Index/product-index.htm. Specific MB and MBP models
206     // that use this are:
207     //          MacBook1,1
208     //          MacBook2,1
209     //          MacBook3,1
210     //          MacBook4,1
211     //          MacBook5,1
212     //          MacBook6,1
213     //          MacBookAir1,1
214     //          MacBookPro1,1
215     //          MacBookPro1,2
216     //          MacBookPro4,1
217     //          MacBookPro5,5
218     {"", "SMCMotionSensor", 5, 40, {{1, 0, 2, 0, 251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, 251}}}};
220 #define SENSOR_COUNT (sizeof(sensors) / sizeof(sensorSpec))
222 #pragma mark Internal prototypes
224 static int getData(sms_acceleration* accel, int calibrated, id logObject, SEL logSelector);
225 static float getAxis(int which, int calibrated);
226 static int signExtend(int value, int size);
227 static NSString* getModelName(void);
228 static NSString* getOSVersion(void);
229 static BOOL loadCalibration(void);
230 static void storeCalibration(void);
231 static void defaultCalibration(void);
232 static void deleteCalibration(void);
233 static int prefIntRead(NSString* prefName, BOOL* success);
234 static void prefIntWrite(NSString* prefName, int prefValue);
235 static float prefFloatRead(NSString* prefName, BOOL* success);
236 static void prefFloatWrite(NSString* prefName, float prefValue);
237 static void prefDelete(NSString* prefName);
238 static void prefSynchronize(void);
239 // static long getMicroseconds(void);
240 float fakeData(NSTimeInterval time);
242 #pragma mark Static variables
244 static int debugging = NO;          // True if debugging (synthetic data)
245 static io_connect_t connection;     // Connection for reading accel values
246 static int running = NO;            // True if we successfully started
247 static unsigned int sensorNum = 0;  // The current index into sensors[]
248 static const char* serviceName;     // The name of the current service
249 static char *iRecord, *oRecord;     // Pointers to read/write records for sensor
250 static int recordSize;              // Size of read/write records
251 static unsigned int function;       // Which kernel function should be used
252 static float zeros[3];              // X, Y and Z zero calibration values
253 static float onegs[3];              // X, Y and Z one-g calibration values
255 #pragma mark Defines
257 // Pattern for building axis letter from axis number
258 #define INT_TO_AXIS(a) (a == 0 ? @"X" : a == 1 ? @"Y" : @"Z")
259 // Name of configuration for given axis' zero (axis specified by integer)
260 #define ZERO_NAME(a) [NSString stringWithFormat:@"%@-Axis-Zero", INT_TO_AXIS(a)]
261 // Name of configuration for given axis' oneg (axis specified by integer)
262 #define ONEG_NAME(a) [NSString stringWithFormat:@"%@-Axis-One-g", INT_TO_AXIS(a)]
263 // Name of "Is calibrated" preference
264 #define CALIBRATED_NAME (@"Calibrated")
265 // Application domain for SeisMac library
266 #define APP_ID ((CFStringRef) @"com.suitable.SeisMacLib")
268 // These #defines make the accelStartup code a LOT easier to read.
269 #undef LOG
270 #define LOG(message)                                            \
271   if (logObject) {                                              \
272     [logObject performSelector:logSelector withObject:message]; \
273   }
274 #define LOG_ARG(format, var1)                                                                    \
275   if (logObject) {                                                                               \
276     [logObject performSelector:logSelector withObject:[NSString stringWithFormat:format, var1]]; \
277   }
278 #define LOG_2ARG(format, var1, var2)                                            \
279   if (logObject) {                                                              \
280     [logObject performSelector:logSelector                                      \
281                     withObject:[NSString stringWithFormat:format, var1, var2]]; \
282   }
283 #define LOG_3ARG(format, var1, var2, var3)                                            \
284   if (logObject) {                                                                    \
285     [logObject performSelector:logSelector                                            \
286                     withObject:[NSString stringWithFormat:format, var1, var2, var3]]; \
287   }
289 #pragma mark Function definitions
291 // This starts up the accelerometer code, trying each possible sensor
292 // specification. Note that for logging purposes it
293 // takes an object and a selector; the object's selector is then invoked
294 // with a single NSString as argument giving progress messages. Example
295 // logging method:
296 //              - (void)logMessage: (NSString *)theString
297 // which would be used in accelStartup's invocation thusly:
298 //              result = accelStartup(self, @selector(logMessage:));
299 // If the object is nil, then no logging is done. Sets calibation from built-in
300 // value table. Returns ACCEL_SUCCESS for success, and other (negative)
301 // values for various failures (returns value indicating result of
302 // most successful trial).
303 int smsStartup(id logObject, SEL logSelector) {
304   io_iterator_t iterator;
305   io_object_t device;
306   kern_return_t result;
307   sms_acceleration accel;
308   int failure_result = SMS_FAIL_MODEL;
310   running = NO;
311   debugging = NO;
313   NSString* modelName = getModelName();
315   LOG_ARG(@"Machine model: %@\n", modelName);
316   LOG_ARG(@"OS X version: %@\n", getOSVersion());
317   LOG_ARG(@"Accelerometer library version: %s\n", SMSLIB_VERSION);
319   for (sensorNum = 0; sensorNum < SENSOR_COUNT; sensorNum++) {
320     // Set up all specs for this type of sensor
321     serviceName = sensors[sensorNum].name;
322     recordSize = sensors[sensorNum].recordSize;
323     function = sensors[sensorNum].function;
325     LOG_3ARG(@"Trying service \"%s\" with selector %d and %d byte record:\n", serviceName, function,
326              recordSize);
328     NSString* targetName = [NSString stringWithCString:sensors[sensorNum].model
329                                               encoding:NSMacOSRomanStringEncoding];
330     LOG_ARG(@"    Comparing model name to target \"%@\": ", targetName);
331     if ([targetName length] == 0 || [modelName hasPrefix:targetName]) {
332       LOG(@"success.\n");
333     } else {
334       LOG(@"failure.\n");
335       // Don't need to increment failure_result.
336       continue;
337     }
339     LOG(@"    Fetching dictionary for service: ");
340     CFMutableDictionaryRef dict = IOServiceMatching(serviceName);
342     if (dict) {
343       LOG(@"success.\n");
344     } else {
345       LOG(@"failure.\n");
346       if (failure_result < SMS_FAIL_DICTIONARY) {
347         failure_result = SMS_FAIL_DICTIONARY;
348       }
349       continue;
350     }
352     LOG(@"    Getting list of matching services: ");
353     result = IOServiceGetMatchingServices(kIOMasterPortDefault, dict, &iterator);
355     if (result == KERN_SUCCESS) {
356       LOG(@"success.\n");
357     } else {
358       LOG_ARG(@"failure, with return value 0x%x.\n", result);
359       if (failure_result < SMS_FAIL_LIST_SERVICES) {
360         failure_result = SMS_FAIL_LIST_SERVICES;
361       }
362       continue;
363     }
365     LOG(@"    Getting first device in list: ");
366     device = IOIteratorNext(iterator);
368     if (device == 0) {
369       LOG(@"failure.\n");
370       if (failure_result < SMS_FAIL_NO_SERVICES) {
371         failure_result = SMS_FAIL_NO_SERVICES;
372       }
373       continue;
374     } else {
375       LOG(@"success.\n");
376       LOG(@"    Opening device: ");
377     }
379     result = IOServiceOpen(device, mach_task_self(), 0, &connection);
381     if (result != KERN_SUCCESS) {
382       LOG_ARG(@"failure, with return value 0x%x.\n", result);
383       IOObjectRelease(device);
384       if (failure_result < SMS_FAIL_OPENING) {
385         failure_result = SMS_FAIL_OPENING;
386       }
387       continue;
388     } else if (connection == 0) {
389       LOG_ARG(@"'success', but didn't get a connection (return value was: 0x%x).\n", result);
390       IOObjectRelease(device);
391       if (failure_result < SMS_FAIL_CONNECTION) {
392         failure_result = SMS_FAIL_CONNECTION;
393       }
394       continue;
395     } else {
396       IOObjectRelease(device);
397       LOG(@"success.\n");
398     }
399     LOG(@"    Testing device.\n");
401     defaultCalibration();
403     iRecord = (char*)malloc(recordSize);
404     oRecord = (char*)malloc(recordSize);
406     running = YES;
407     result = getData(&accel, true, logObject, logSelector);
408     running = NO;
410     if (result) {
411       LOG_ARG(@"    Failure testing device, with result 0x%x.\n", result);
412       free(iRecord);
413       iRecord = 0;
414       free(oRecord);
415       oRecord = 0;
416       if (failure_result < SMS_FAIL_ACCESS) {
417         failure_result = SMS_FAIL_ACCESS;
418       }
419       continue;
420     } else {
421       LOG(@"    Success testing device!\n");
422       running = YES;
423       return SMS_SUCCESS;
424     }
425   }
426   return failure_result;
429 // This starts up the library in debug mode, ignoring the actual hardware.
430 // Returned data is in the form of 1Hz sine waves, with the X, Y and Z
431 // axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5);
432 // "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0,
433 // Z axes centered on 1 (calibrated) or 256 (uncalibrated).
434 // Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS.
435 int smsDebugStartup(id logObject, SEL logSelector) {
436   LOG(@"Starting up in debug mode\n");
437   debugging = YES;
438   return SMS_SUCCESS;
441 // Returns the current calibration values.
442 void smsGetCalibration(sms_calibration* calibrationRecord) {
443   int x;
445   for (x = 0; x < 3; x++) {
446     calibrationRecord->zeros[x] = (debugging ? 0 : zeros[x]);
447     calibrationRecord->onegs[x] = (debugging ? 256 : onegs[x]);
448   }
451 // Sets the calibration, but does NOT store it as a preference. If the argument
452 // is nil then the current calibration is set from the built-in value table.
453 void smsSetCalibration(sms_calibration* calibrationRecord) {
454   int x;
456   if (!debugging) {
457     if (calibrationRecord) {
458       for (x = 0; x < 3; x++) {
459         zeros[x] = calibrationRecord->zeros[x];
460         onegs[x] = calibrationRecord->onegs[x];
461       }
462     } else {
463       defaultCalibration();
464     }
465   }
468 // Stores the current calibration values as a stored preference.
469 void smsStoreCalibration(void) {
470   if (!debugging) storeCalibration();
473 // Loads the stored preference values into the current calibration.
474 // Returns YES if successful.
475 BOOL smsLoadCalibration(void) {
476   if (debugging) {
477     return YES;
478   } else if (loadCalibration()) {
479     return YES;
480   } else {
481     defaultCalibration();
482     return NO;
483   }
486 // Deletes any stored calibration, and then takes the current calibration values
487 // from the built-in value table.
488 void smsDeleteCalibration(void) {
489   if (!debugging) {
490     deleteCalibration();
491     defaultCalibration();
492   }
495 // Fills in the accel record with calibrated acceleration data. Takes
496 // 1-2ms to return a value. Returns 0 if success, error number if failure.
497 int smsGetData(sms_acceleration* accel) {
498   NSTimeInterval time;
499   if (debugging) {
500     usleep(1500);  // Usually takes 1-2 milliseconds
501     time = [NSDate timeIntervalSinceReferenceDate];
502     accel->x = fakeData(time) / 5;
503     accel->y = fakeData(time - 1) / 5;
504     accel->z = fakeData(time - 2) / 5 + 1.0;
505     return true;
506   } else {
507     return getData(accel, true, nil, nil);
508   }
511 // Fills in the accel record with uncalibrated acceleration data.
512 // Returns 0 if success, error number if failure.
513 int smsGetUncalibratedData(sms_acceleration* accel) {
514   NSTimeInterval time;
515   if (debugging) {
516     usleep(1500);  // Usually takes 1-2 milliseconds
517     time = [NSDate timeIntervalSinceReferenceDate];
518     accel->x = fakeData(time) * 256 / 5;
519     accel->y = fakeData(time - 1) * 256 / 5;
520     accel->z = fakeData(time - 2) * 256 / 5 + 256;
521     return true;
522   } else {
523     return getData(accel, false, nil, nil);
524   }
527 // Returns the length of a raw block of data for the current type of sensor.
528 int smsGetBufferLength(void) {
529   if (debugging) {
530     return 0;
531   } else if (running) {
532     return sensors[sensorNum].recordSize;
533   } else {
534     return 0;
535   }
538 // Takes a pointer to accelGetRawLength() bytes; sets those bytes
539 // to return value from sensor. Make darn sure the buffer length is right!
540 void smsGetBufferData(char* buffer) {
541   IOItemCount iSize = recordSize;
542   IOByteCount oSize = recordSize;
543   kern_return_t result;
545   if (debugging || running == NO) {
546     return;
547   }
549   memset(iRecord, 1, iSize);
550   memset(buffer, 0, oSize);
551 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
552   const size_t InStructSize = recordSize;
553   size_t OutStructSize = recordSize;
554   result =
555       IOConnectCallStructMethod(connection,
556                                 function,  // magic kernel function number
557                                 (const void*)iRecord, InStructSize, (void*)buffer, &OutStructSize);
558 #else   // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
559   result = IOConnectMethodStructureIStructureO(connection,
560                                                function,  // magic kernel function number
561                                                iSize, &oSize, iRecord, buffer);
562 #endif  // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
564   if (result != KERN_SUCCESS) {
565     running = NO;
566   }
569 // This returns an NSString describing the current calibration in
570 // human-readable form. Also include a description of the machine.
571 NSString* smsGetCalibrationDescription(void) {
572   BOOL success;
573   NSMutableString* s = [[NSMutableString alloc] init];
575   if (debugging) {
576     [s release];
577     return @"Debugging!";
578   }
580   [s appendString:@"---- SeisMac Calibration Record ----\n \n"];
581   [s appendFormat:@"Machine model: %@\n", getModelName()];
582   [s appendFormat:@"OS X build: %@\n", getOSVersion()];
583   [s appendFormat:@"SeisMacLib version %s, record %d\n \n", SMSLIB_VERSION, sensorNum];
584   [s appendFormat:@"Using service \"%s\", function index %d, size %d\n \n", serviceName, function,
585                   recordSize];
586   if (prefIntRead(CALIBRATED_NAME, &success) && success) {
587     [s appendString:@"Calibration values (from calibration):\n"];
588   } else {
589     [s appendString:@"Calibration values (from defaults):\n"];
590   }
591   [s appendFormat:@"    X-Axis-Zero  = %.2f\n", zeros[0]];
592   [s appendFormat:@"    X-Axis-One-g = %.2f\n", onegs[0]];
593   [s appendFormat:@"    Y-Axis-Zero  = %.2f\n", zeros[1]];
594   [s appendFormat:@"    Y-Axis-One-g = %.2f\n", onegs[1]];
595   [s appendFormat:@"    Z-Axis-Zero  = %.2f\n", zeros[2]];
596   [s appendFormat:@"    Z-Axis-One-g = %.2f\n \n", onegs[2]];
597   [s appendString:@"---- End Record ----\n"];
598   return s;
601 // Shuts down the accelerometer.
602 void smsShutdown(void) {
603   if (!debugging) {
604     running = NO;
605     if (iRecord) free(iRecord);
606     if (oRecord) free(oRecord);
607     IOServiceClose(connection);
608   }
611 #pragma mark Internal functions
613 // Loads the current calibration from the stored preferences.
614 // Returns true iff successful.
615 BOOL loadCalibration(void) {
616   BOOL thisSuccess, allSuccess;
617   int x;
619   prefSynchronize();
621   if (prefIntRead(CALIBRATED_NAME, &thisSuccess) && thisSuccess) {
622     // Calibrated. Set all values from saved values.
623     allSuccess = YES;
624     for (x = 0; x < 3; x++) {
625       zeros[x] = prefFloatRead(ZERO_NAME(x), &thisSuccess);
626       allSuccess &= thisSuccess;
627       onegs[x] = prefFloatRead(ONEG_NAME(x), &thisSuccess);
628       allSuccess &= thisSuccess;
629     }
630     return allSuccess;
631   }
633   return NO;
636 // Stores the current calibration into the stored preferences.
637 static void storeCalibration(void) {
638   int x;
639   prefIntWrite(CALIBRATED_NAME, 1);
640   for (x = 0; x < 3; x++) {
641     prefFloatWrite(ZERO_NAME(x), zeros[x]);
642     prefFloatWrite(ONEG_NAME(x), onegs[x]);
643   }
644   prefSynchronize();
647 // Sets the calibration to its default values.
648 void defaultCalibration(void) {
649   int x;
650   for (x = 0; x < 3; x++) {
651     zeros[x] = sensors[sensorNum].axes[x].zerog;
652     onegs[x] = sensors[sensorNum].axes[x].oneg;
653   }
656 // Deletes the stored preferences.
657 static void deleteCalibration(void) {
658   int x;
660   prefDelete(CALIBRATED_NAME);
661   for (x = 0; x < 3; x++) {
662     prefDelete(ZERO_NAME(x));
663     prefDelete(ONEG_NAME(x));
664   }
665   prefSynchronize();
668 // Read a named floating point value from the stored preferences. Sets
669 // the success boolean based on, you guessed it, whether it succeeds.
670 static float prefFloatRead(NSString* prefName, BOOL* success) {
671   float result = 0.0f;
673   CFPropertyListRef ref = CFPreferencesCopyAppValue((CFStringRef)prefName, APP_ID);
674   // If there isn't such a preference, fail
675   if (ref == NULL) {
676     *success = NO;
677     return result;
678   }
679   CFTypeID typeID = CFGetTypeID(ref);
680   // Is it a number?
681   if (typeID == CFNumberGetTypeID()) {
682     // Is it a floating point number?
683     if (CFNumberIsFloatType((CFNumberRef)ref)) {
684       // Yup: grab it.
685       *success = CFNumberGetValue((__CFNumber*)ref, kCFNumberFloat32Type, &result);
686     } else {
687       // Nope: grab as an integer, and convert to a float.
688       long num;
689       if (CFNumberGetValue((CFNumberRef)ref, kCFNumberLongType, &num)) {
690         result = num;
691         *success = YES;
692       } else {
693         *success = NO;
694       }
695     }
696     // Or is it a string (e.g. set by the command line "defaults" command)?
697   } else if (typeID == CFStringGetTypeID()) {
698     result = (float)CFStringGetDoubleValue((CFStringRef)ref);
699     *success = YES;
700   } else {
701     // Can't convert to a number: fail.
702     *success = NO;
703   }
704   CFRelease(ref);
705   return result;
708 // Writes a named floating point value to the stored preferences.
709 static void prefFloatWrite(NSString* prefName, float prefValue) {
710   CFNumberRef cfFloat = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &prefValue);
711   CFPreferencesSetAppValue((CFStringRef)prefName, cfFloat, APP_ID);
712   CFRelease(cfFloat);
715 // Reads a named integer value from the stored preferences.
716 static int prefIntRead(NSString* prefName, BOOL* success) {
717   Boolean internalSuccess;
718   CFIndex result = CFPreferencesGetAppIntegerValue((CFStringRef)prefName, APP_ID, &internalSuccess);
719   *success = internalSuccess;
721   return result;
724 // Writes a named integer value to the stored preferences.
725 static void prefIntWrite(NSString* prefName, int prefValue) {
726   CFPreferencesSetAppValue((CFStringRef)prefName, (CFNumberRef)[NSNumber numberWithInt:prefValue],
727                            APP_ID);
730 // Deletes the named preference values.
731 static void prefDelete(NSString* prefName) {
732   CFPreferencesSetAppValue((CFStringRef)prefName, NULL, APP_ID);
735 // Synchronizes the local preferences with the stored preferences.
736 static void prefSynchronize(void) { CFPreferencesAppSynchronize(APP_ID); }
738 // Internal version of accelGetData, with logging
739 int getData(sms_acceleration* accel, int calibrated, id logObject, SEL logSelector) {
740   IOItemCount iSize = recordSize;
741   IOByteCount oSize = recordSize;
742   kern_return_t result;
744   if (running == NO) {
745     return -1;
746   }
748   memset(iRecord, 1, iSize);
749   memset(oRecord, 0, oSize);
751   LOG_2ARG(@"    Querying device (%u, %d): ", sensors[sensorNum].function,
752            sensors[sensorNum].recordSize);
754 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
755   const size_t InStructSize = recordSize;
756   size_t OutStructSize = recordSize;
757   result =
758       IOConnectCallStructMethod(connection,
759                                 function,  // magic kernel function number
760                                 (const void*)iRecord, InStructSize, (void*)oRecord, &OutStructSize);
761 #else   // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
762   result = IOConnectMethodStructureIStructureO(connection,
763                                                function,  // magic kernel function number
764                                                iSize, &oSize, iRecord, oRecord);
765 #endif  // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
767   if (result != KERN_SUCCESS) {
768     LOG(@"failed.\n");
769     running = NO;
770     return result;
771   } else {
772     LOG(@"succeeded.\n");
774     accel->x = getAxis(0, calibrated);
775     accel->y = getAxis(1, calibrated);
776     accel->z = getAxis(2, calibrated);
777     return 0;
778   }
781 // Given the returned record, extracts the value of the given axis. If
782 // calibrated, then zero G is 0.0, and one G is 1.0.
783 float getAxis(int which, int calibrated) {
784   // Get various values (to make code cleaner)
785   int indx = sensors[sensorNum].axes[which].index;
786   int size = sensors[sensorNum].axes[which].size;
787   float zerog = zeros[which];
788   float oneg = onegs[which];
789   // Storage for value to be returned
790   int value = 0;
792   // Although the values in the returned record should have the proper
793   // endianness, we still have to get it into the proper end of value.
794 #if (BYTE_ORDER == BIG_ENDIAN)
795   // On PowerPC processors
796   memcpy(((char*)&value) + (sizeof(int) - size), &oRecord[indx], size);
797 #endif
798 #if (BYTE_ORDER == LITTLE_ENDIAN)
799   // On Intel processors
800   memcpy(&value, &oRecord[indx], size);
801 #endif
803   value = signExtend(value, size);
805   if (calibrated) {
806     // Scale and shift for zero.
807     return ((float)(value - zerog)) / oneg;
808   } else {
809     return value;
810   }
813 // Extends the sign, given the length of the value.
814 int signExtend(int value, int size) {
815   // Extend sign
816   switch (size) {
817     case 1:
818       if (value & 0x00000080) value |= 0xffffff00;
819       break;
820     case 2:
821       if (value & 0x00008000) value |= 0xffff0000;
822       break;
823     case 3:
824       if (value & 0x00800000) value |= 0xff000000;
825       break;
826   }
827   return value;
830 // Returns the model name of the computer (e.g. "MacBookPro1,1")
831 NSString* getModelName(void) {
832   char model[32];
833   size_t len = sizeof(model);
834   int name[2] = {CTL_HW, HW_MODEL};
835   NSString* result;
837   if (sysctl(name, 2, &model, &len, NULL, 0) == 0) {
838     result = [NSString stringWithFormat:@"%s", model];
839   } else {
840     result = @"";
841   }
843   return result;
846 // Returns the current OS X version and build (e.g. "10.4.7 (build 8J2135a)")
847 NSString* getOSVersion(void) {
848   NSDictionary* dict = [NSDictionary
849       dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
850   NSString* versionString = [dict objectForKey:@"ProductVersion"];
851   NSString* buildString = [dict objectForKey:@"ProductBuildVersion"];
852   NSString* wholeString = [NSString stringWithFormat:@"%@ (build %@)", versionString, buildString];
853   return wholeString;
856 // Returns time within the current second in microseconds.
857 // long getMicroseconds() {
858 //      struct timeval t;
859 //      gettimeofday(&t, 0);
860 //      return t.tv_usec;
863 // Returns fake data given the time. Range is +/-1.
864 float fakeData(NSTimeInterval time) {
865   long secs = lround(floor(time));
866   int secsMod3 = secs % 3;
867   double angle = time * 10 * M_PI * 2;
868   double mag = exp(-(time - (secs - secsMod3)) * 2);
869   return sin(angle) * mag;