tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

smslib.mm (30454B)


      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 */
     49 
     50 #import <IOKit/IOKitLib.h>
     51 #import <sys/sysctl.h>
     52 #import <math.h>
     53 #import "smslib.h"
     54 
     55 #pragma mark Internal structures
     56 
     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;
     66 
     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;
     75 
     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.
     80 //
     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 50-53
     86 //   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
     89 //
     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 "" matches
     94 //   any model).
     95 // - If no model-specific record's access fails, then try each model-independent
     96 //   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
    146    // '07.
    147    // NOTE! The 17" machines have the signs of their X and Y axes reversed
    148    // from this calibration, but there's no clear way to discriminate between
    149    // the two machines.
    150    {"MacBookPro3,1",
    151     "SMCMotionSensor",
    152     5,
    153     40,
    154     {{1, 0, 2, 0, -251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, -251}}},
    155    // ... specs?
    156    {"MacBook5,2",
    157     "SMCMotionSensor",
    158     5,
    159     40,
    160     {{1, 0, 2, 0, -251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, -251}}},
    161    // ... specs?
    162    {"MacBookPro5,1",
    163     "SMCMotionSensor",
    164     5,
    165     40,
    166     {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}},
    167    // ... specs?
    168    {"MacBookPro5,2",
    169     "SMCMotionSensor",
    170     5,
    171     40,
    172     {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}},
    173    // This is speculative, based on a single user's report. Looks like the X
    174    // and Y axes
    175    // are swapped. This is true for no other known Appple laptop.
    176    {"MacBookPro5,3",
    177     "SMCMotionSensor",
    178     5,
    179     40,
    180     {{1, 2, 2, 0, -251}, {1, 0, 2, 0, -251}, {1, 4, 2, 0, -251}}},
    181    // ... specs?
    182    {"MacBookPro5,4",
    183     "SMCMotionSensor",
    184     5,
    185     40,
    186     {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}},
    187    // ****** Model-independent methods ******
    188    // Seen once with PowerBook6,8 under system 10.3.9; I suspect
    189    // other G4-based 10.3.* systems might use this
    190    {"",
    191     "IOI2CMotionSensor",
    192     24,
    193     60,
    194     {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}},
    195    // PowerBook5,6 , PowerBook5,7 , PowerBook6,7 , PowerBook6,8
    196    // under OS X 10.4.*
    197    {"",
    198     "IOI2CMotionSensor",
    199     21,
    200     60,
    201     {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}},
    202    // PowerBook5,8 , PowerBook5,9 under OS X 10.4.*
    203    {"",
    204     "PMUMotionSensor",
    205     21,
    206     60,
    207     {// Each has two out of three gains negative, but it's different
    208      // for the different models. So, this will be right in two out
    209      // of three axis for either model.
    210      {1, 0, 1, 0, -51.5},
    211      {1, 1, 1, -6, -51.5},
    212      {1, 2, 1, 0, -51.5}}},
    213    // All MacBook, MacBookPro models. Hardware (at least on early MacBookPro
    214    // 15")
    215    // is Kionix KXM52-1050 three-axis accelerometer chip. Data is at
    216    // http://kionix.com/Product-Index/product-index.htm. Specific MB and MBP
    217    // models
    218    // that use this are:
    219    //		MacBook1,1
    220    //		MacBook2,1
    221    //		MacBook3,1
    222    //		MacBook4,1
    223    //		MacBook5,1
    224    //		MacBook6,1
    225    //		MacBookAir1,1
    226    //		MacBookPro1,1
    227    //		MacBookPro1,2
    228    //		MacBookPro4,1
    229    //		MacBookPro5,5
    230    {"",
    231     "SMCMotionSensor",
    232     5,
    233     40,
    234     {{1, 0, 2, 0, 251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, 251}}}};
    235 
    236 #define SENSOR_COUNT (sizeof(sensors) / sizeof(sensorSpec))
    237 
    238 #pragma mark Internal prototypes
    239 
    240 static int getData(sms_acceleration* accel, int calibrated, id logObject,
    241                   SEL logSelector);
    242 static float getAxis(int which, int calibrated);
    243 static int signExtend(int value, int size);
    244 static NSString* getModelName(void);
    245 static NSString* getOSVersion(void);
    246 static BOOL loadCalibration(void);
    247 static void storeCalibration(void);
    248 static void defaultCalibration(void);
    249 static void deleteCalibration(void);
    250 static int prefIntRead(NSString* prefName, BOOL* success);
    251 static void prefIntWrite(NSString* prefName, int prefValue);
    252 static float prefFloatRead(NSString* prefName, BOOL* success);
    253 static void prefFloatWrite(NSString* prefName, float prefValue);
    254 static void prefDelete(NSString* prefName);
    255 static void prefSynchronize(void);
    256 // static long getMicroseconds(void);
    257 float fakeData(NSTimeInterval time);
    258 
    259 #pragma mark Static variables
    260 
    261 static int debugging = NO;          // True if debugging (synthetic data)
    262 static io_connect_t connection;     // Connection for reading accel values
    263 static int running = NO;            // True if we successfully started
    264 static unsigned int sensorNum = 0;  // The current index into sensors[]
    265 static const char* serviceName;     // The name of the current service
    266 static char *iRecord, *oRecord;     // Pointers to read/write records for sensor
    267 static int recordSize;              // Size of read/write records
    268 static unsigned int function;       // Which kernel function should be used
    269 static float zeros[3];              // X, Y and Z zero calibration values
    270 static float onegs[3];              // X, Y and Z one-g calibration values
    271 
    272 #pragma mark Defines
    273 
    274 // Pattern for building axis letter from axis number
    275 #define INT_TO_AXIS(a) (a == 0 ? @"X" : a == 1 ? @"Y" : @"Z")
    276 // Name of configuration for given axis' zero (axis specified by integer)
    277 #define ZERO_NAME(a) [NSString stringWithFormat:@"%@-Axis-Zero", INT_TO_AXIS(a)]
    278 // Name of configuration for given axis' oneg (axis specified by integer)
    279 #define ONEG_NAME(a) \
    280  [NSString stringWithFormat:@"%@-Axis-One-g", INT_TO_AXIS(a)]
    281 // Name of "Is calibrated" preference
    282 #define CALIBRATED_NAME (@"Calibrated")
    283 // Application domain for SeisMac library
    284 #define APP_ID ((CFStringRef) @"com.suitable.SeisMacLib")
    285 
    286 // These #defines make the accelStartup code a LOT easier to read.
    287 #undef LOG
    288 #define LOG(message)                                            \
    289  if (logObject) {                                              \
    290    [logObject performSelector:logSelector withObject:message]; \
    291  }
    292 #define LOG_ARG(format, var1)                                             \
    293  if (logObject) {                                                        \
    294    [logObject performSelector:logSelector                                \
    295                    withObject:[NSString stringWithFormat:format, var1]]; \
    296  }
    297 #define LOG_2ARG(format, var1, var2)                                     \
    298  if (logObject) {                                                       \
    299    [logObject                                                           \
    300        performSelector:logSelector                                      \
    301             withObject:[NSString stringWithFormat:format, var1, var2]]; \
    302  }
    303 #define LOG_3ARG(format, var1, var2, var3)                                     \
    304  if (logObject) {                                                             \
    305    [logObject                                                                 \
    306        performSelector:logSelector                                            \
    307             withObject:[NSString stringWithFormat:format, var1, var2, var3]]; \
    308  }
    309 
    310 #pragma mark Function definitions
    311 
    312 // This starts up the accelerometer code, trying each possible sensor
    313 // specification. Note that for logging purposes it
    314 // takes an object and a selector; the object's selector is then invoked
    315 // with a single NSString as argument giving progress messages. Example
    316 // logging method:
    317 //		- (void)logMessage: (NSString *)theString
    318 // which would be used in accelStartup's invocation thusly:
    319 //		result = accelStartup(self, @selector(logMessage:));
    320 // If the object is nil, then no logging is done. Sets calibation from built-in
    321 // value table. Returns ACCEL_SUCCESS for success, and other (negative)
    322 // values for various failures (returns value indicating result of
    323 // most successful trial).
    324 int smsStartup(id logObject, SEL logSelector) {
    325  io_iterator_t iterator;
    326  io_object_t device;
    327  kern_return_t result;
    328  sms_acceleration accel;
    329  int failure_result = SMS_FAIL_MODEL;
    330 
    331  running = NO;
    332  debugging = NO;
    333 
    334  NSString* modelName = getModelName();
    335 
    336  LOG_ARG(@"Machine model: %@\n", modelName);
    337  LOG_ARG(@"OS X version: %@\n", getOSVersion());
    338  LOG_ARG(@"Accelerometer library version: %s\n", SMSLIB_VERSION);
    339 
    340  for (sensorNum = 0; sensorNum < SENSOR_COUNT; sensorNum++) {
    341    // Set up all specs for this type of sensor
    342    serviceName = sensors[sensorNum].name;
    343    recordSize = sensors[sensorNum].recordSize;
    344    function = sensors[sensorNum].function;
    345 
    346    LOG_3ARG(@"Trying service \"%s\" with selector %d and %d byte record:\n",
    347             serviceName, function, recordSize);
    348 
    349    NSString* targetName =
    350        [NSString stringWithCString:sensors[sensorNum].model
    351                           encoding:NSMacOSRomanStringEncoding];
    352    LOG_ARG(@"    Comparing model name to target \"%@\": ", targetName);
    353    if ([targetName length] == 0 || [modelName hasPrefix:targetName]) {
    354      LOG(@"success.\n");
    355    } else {
    356      LOG(@"failure.\n");
    357      // Don't need to increment failure_result.
    358      continue;
    359    }
    360 
    361    LOG(@"    Fetching dictionary for service: ");
    362    CFMutableDictionaryRef dict = IOServiceMatching(serviceName);
    363 
    364    if (dict) {
    365      LOG(@"success.\n");
    366    } else {
    367      LOG(@"failure.\n");
    368      if (failure_result < SMS_FAIL_DICTIONARY) {
    369        failure_result = SMS_FAIL_DICTIONARY;
    370      }
    371      continue;
    372    }
    373 
    374    LOG(@"    Getting list of matching services: ");
    375    result =
    376        IOServiceGetMatchingServices(kIOMasterPortDefault, dict, &iterator);
    377 
    378    if (result == KERN_SUCCESS) {
    379      LOG(@"success.\n");
    380    } else {
    381      LOG_ARG(@"failure, with return value 0x%x.\n", result);
    382      if (failure_result < SMS_FAIL_LIST_SERVICES) {
    383        failure_result = SMS_FAIL_LIST_SERVICES;
    384      }
    385      continue;
    386    }
    387 
    388    LOG(@"    Getting first device in list: ");
    389    device = IOIteratorNext(iterator);
    390 
    391    if (device == 0) {
    392      LOG(@"failure.\n");
    393      if (failure_result < SMS_FAIL_NO_SERVICES) {
    394        failure_result = SMS_FAIL_NO_SERVICES;
    395      }
    396      continue;
    397    } else {
    398      LOG(@"success.\n");
    399      LOG(@"    Opening device: ");
    400    }
    401 
    402    result = IOServiceOpen(device, mach_task_self(), 0, &connection);
    403 
    404    if (result != KERN_SUCCESS) {
    405      LOG_ARG(@"failure, with return value 0x%x.\n", result);
    406      IOObjectRelease(device);
    407      if (failure_result < SMS_FAIL_OPENING) {
    408        failure_result = SMS_FAIL_OPENING;
    409      }
    410      continue;
    411    } else if (connection == 0) {
    412      LOG_ARG(
    413          @"'success', but didn't get a connection (return value was: 0x%x).\n",
    414          result);
    415      IOObjectRelease(device);
    416      if (failure_result < SMS_FAIL_CONNECTION) {
    417        failure_result = SMS_FAIL_CONNECTION;
    418      }
    419      continue;
    420    } else {
    421      IOObjectRelease(device);
    422      LOG(@"success.\n");
    423    }
    424    LOG(@"    Testing device.\n");
    425 
    426    defaultCalibration();
    427 
    428    iRecord = (char*)malloc(recordSize);
    429    oRecord = (char*)malloc(recordSize);
    430 
    431    running = YES;
    432    result = getData(&accel, true, logObject, logSelector);
    433    running = NO;
    434 
    435    if (result) {
    436      LOG_ARG(@"    Failure testing device, with result 0x%x.\n", result);
    437      free(iRecord);
    438      iRecord = 0;
    439      free(oRecord);
    440      oRecord = 0;
    441      if (failure_result < SMS_FAIL_ACCESS) {
    442        failure_result = SMS_FAIL_ACCESS;
    443      }
    444      continue;
    445    } else {
    446      LOG(@"    Success testing device!\n");
    447      running = YES;
    448      return SMS_SUCCESS;
    449    }
    450  }
    451  return failure_result;
    452 }
    453 
    454 // This starts up the library in debug mode, ignoring the actual hardware.
    455 // Returned data is in the form of 1Hz sine waves, with the X, Y and Z
    456 // axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5);
    457 // "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0,
    458 // Z axes centered on 1 (calibrated) or 256 (uncalibrated).
    459 // Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS.
    460 int smsDebugStartup(id logObject, SEL logSelector) {
    461  LOG(@"Starting up in debug mode\n");
    462  debugging = YES;
    463  return SMS_SUCCESS;
    464 }
    465 
    466 // Returns the current calibration values.
    467 void smsGetCalibration(sms_calibration* calibrationRecord) {
    468  int x;
    469 
    470  for (x = 0; x < 3; x++) {
    471    calibrationRecord->zeros[x] = (debugging ? 0 : zeros[x]);
    472    calibrationRecord->onegs[x] = (debugging ? 256 : onegs[x]);
    473  }
    474 }
    475 
    476 // Sets the calibration, but does NOT store it as a preference. If the argument
    477 // is nil then the current calibration is set from the built-in value table.
    478 void smsSetCalibration(sms_calibration* calibrationRecord) {
    479  int x;
    480 
    481  if (!debugging) {
    482    if (calibrationRecord) {
    483      for (x = 0; x < 3; x++) {
    484        zeros[x] = calibrationRecord->zeros[x];
    485        onegs[x] = calibrationRecord->onegs[x];
    486      }
    487    } else {
    488      defaultCalibration();
    489    }
    490  }
    491 }
    492 
    493 // Stores the current calibration values as a stored preference.
    494 void smsStoreCalibration(void) {
    495  if (!debugging) storeCalibration();
    496 }
    497 
    498 // Loads the stored preference values into the current calibration.
    499 // Returns YES if successful.
    500 BOOL smsLoadCalibration(void) {
    501  if (debugging) {
    502    return YES;
    503  } else if (loadCalibration()) {
    504    return YES;
    505  } else {
    506    defaultCalibration();
    507    return NO;
    508  }
    509 }
    510 
    511 // Deletes any stored calibration, and then takes the current calibration values
    512 // from the built-in value table.
    513 void smsDeleteCalibration(void) {
    514  if (!debugging) {
    515    deleteCalibration();
    516    defaultCalibration();
    517  }
    518 }
    519 
    520 // Fills in the accel record with calibrated acceleration data. Takes
    521 // 1-2ms to return a value. Returns 0 if success, error number if failure.
    522 int smsGetData(sms_acceleration* accel) {
    523  NSTimeInterval time;
    524  if (debugging) {
    525    usleep(1500);  // Usually takes 1-2 milliseconds
    526    time = [NSDate timeIntervalSinceReferenceDate];
    527    accel->x = fakeData(time) / 5;
    528    accel->y = fakeData(time - 1) / 5;
    529    accel->z = fakeData(time - 2) / 5 + 1.0;
    530    return true;
    531  } else {
    532    return getData(accel, true, nil, nil);
    533  }
    534 }
    535 
    536 // Fills in the accel record with uncalibrated acceleration data.
    537 // Returns 0 if success, error number if failure.
    538 int smsGetUncalibratedData(sms_acceleration* accel) {
    539  NSTimeInterval time;
    540  if (debugging) {
    541    usleep(1500);  // Usually takes 1-2 milliseconds
    542    time = [NSDate timeIntervalSinceReferenceDate];
    543    accel->x = fakeData(time) * 256 / 5;
    544    accel->y = fakeData(time - 1) * 256 / 5;
    545    accel->z = fakeData(time - 2) * 256 / 5 + 256;
    546    return true;
    547  } else {
    548    return getData(accel, false, nil, nil);
    549  }
    550 }
    551 
    552 // Returns the length of a raw block of data for the current type of sensor.
    553 int smsGetBufferLength(void) {
    554  if (debugging) {
    555    return 0;
    556  } else if (running) {
    557    return sensors[sensorNum].recordSize;
    558  } else {
    559    return 0;
    560  }
    561 }
    562 
    563 // Takes a pointer to accelGetRawLength() bytes; sets those bytes
    564 // to return value from sensor. Make darn sure the buffer length is right!
    565 void smsGetBufferData(char* buffer) {
    566  IOItemCount iSize = recordSize;
    567  IOByteCount oSize = recordSize;
    568  kern_return_t result;
    569 
    570  if (debugging || running == NO) {
    571    return;
    572  }
    573 
    574  memset(iRecord, 1, iSize);
    575  memset(buffer, 0, oSize);
    576 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
    577  const size_t InStructSize = recordSize;
    578  size_t OutStructSize = recordSize;
    579  result = IOConnectCallStructMethod(connection,
    580                                     function,  // magic kernel function number
    581                                     (const void*)iRecord, InStructSize,
    582                                     (void*)buffer, &OutStructSize);
    583 #else   // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
    584  result = IOConnectMethodStructureIStructureO(
    585      connection,
    586      function,  // magic kernel function number
    587      iSize, &oSize, iRecord, buffer);
    588 #endif  // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
    589 
    590  if (result != KERN_SUCCESS) {
    591    running = NO;
    592  }
    593 }
    594 
    595 // This returns an NSString describing the current calibration in
    596 // human-readable form. Also include a description of the machine.
    597 NSString* smsGetCalibrationDescription(void) {
    598  BOOL success;
    599  NSMutableString* s = [[NSMutableString alloc] init];
    600 
    601  if (debugging) {
    602    [s release];
    603    return @"Debugging!";
    604  }
    605 
    606  [s appendString:@"---- SeisMac Calibration Record ----\n \n"];
    607  [s appendFormat:@"Machine model: %@\n", getModelName()];
    608  [s appendFormat:@"OS X build: %@\n", getOSVersion()];
    609  [s appendFormat:@"SeisMacLib version %s, record %d\n \n", SMSLIB_VERSION,
    610                  sensorNum];
    611  [s appendFormat:@"Using service \"%s\", function index %d, size %d\n \n",
    612                  serviceName, function, recordSize];
    613  if (prefIntRead(CALIBRATED_NAME, &success) && success) {
    614    [s appendString:@"Calibration values (from calibration):\n"];
    615  } else {
    616    [s appendString:@"Calibration values (from defaults):\n"];
    617  }
    618  [s appendFormat:@"    X-Axis-Zero  = %.2f\n", zeros[0]];
    619  [s appendFormat:@"    X-Axis-One-g = %.2f\n", onegs[0]];
    620  [s appendFormat:@"    Y-Axis-Zero  = %.2f\n", zeros[1]];
    621  [s appendFormat:@"    Y-Axis-One-g = %.2f\n", onegs[1]];
    622  [s appendFormat:@"    Z-Axis-Zero  = %.2f\n", zeros[2]];
    623  [s appendFormat:@"    Z-Axis-One-g = %.2f\n \n", onegs[2]];
    624  [s appendString:@"---- End Record ----\n"];
    625  return s;
    626 }
    627 
    628 // Shuts down the accelerometer.
    629 void smsShutdown(void) {
    630  if (!debugging) {
    631    running = NO;
    632    if (iRecord) free(iRecord);
    633    if (oRecord) free(oRecord);
    634    IOServiceClose(connection);
    635  }
    636 }
    637 
    638 #pragma mark Internal functions
    639 
    640 // Loads the current calibration from the stored preferences.
    641 // Returns true iff successful.
    642 BOOL loadCalibration(void) {
    643  BOOL thisSuccess, allSuccess;
    644  int x;
    645 
    646  prefSynchronize();
    647 
    648  if (prefIntRead(CALIBRATED_NAME, &thisSuccess) && thisSuccess) {
    649    // Calibrated. Set all values from saved values.
    650    allSuccess = YES;
    651    for (x = 0; x < 3; x++) {
    652      zeros[x] = prefFloatRead(ZERO_NAME(x), &thisSuccess);
    653      allSuccess &= thisSuccess;
    654      onegs[x] = prefFloatRead(ONEG_NAME(x), &thisSuccess);
    655      allSuccess &= thisSuccess;
    656    }
    657    return allSuccess;
    658  }
    659 
    660  return NO;
    661 }
    662 
    663 // Stores the current calibration into the stored preferences.
    664 static void storeCalibration(void) {
    665  int x;
    666  prefIntWrite(CALIBRATED_NAME, 1);
    667  for (x = 0; x < 3; x++) {
    668    prefFloatWrite(ZERO_NAME(x), zeros[x]);
    669    prefFloatWrite(ONEG_NAME(x), onegs[x]);
    670  }
    671  prefSynchronize();
    672 }
    673 
    674 // Sets the calibration to its default values.
    675 void defaultCalibration(void) {
    676  int x;
    677  for (x = 0; x < 3; x++) {
    678    zeros[x] = sensors[sensorNum].axes[x].zerog;
    679    onegs[x] = sensors[sensorNum].axes[x].oneg;
    680  }
    681 }
    682 
    683 // Deletes the stored preferences.
    684 static void deleteCalibration(void) {
    685  int x;
    686 
    687  prefDelete(CALIBRATED_NAME);
    688  for (x = 0; x < 3; x++) {
    689    prefDelete(ZERO_NAME(x));
    690    prefDelete(ONEG_NAME(x));
    691  }
    692  prefSynchronize();
    693 }
    694 
    695 // Read a named floating point value from the stored preferences. Sets
    696 // the success boolean based on, you guessed it, whether it succeeds.
    697 static float prefFloatRead(NSString* prefName, BOOL* success) {
    698  float result = 0.0f;
    699 
    700  CFPropertyListRef ref =
    701      CFPreferencesCopyAppValue((CFStringRef)prefName, APP_ID);
    702  // If there isn't such a preference, fail
    703  if (ref == NULL) {
    704    *success = NO;
    705    return result;
    706  }
    707  CFTypeID typeID = CFGetTypeID(ref);
    708  // Is it a number?
    709  if (typeID == CFNumberGetTypeID()) {
    710    // Is it a floating point number?
    711    if (CFNumberIsFloatType((CFNumberRef)ref)) {
    712      // Yup: grab it.
    713      *success =
    714          CFNumberGetValue((__CFNumber*)ref, kCFNumberFloat32Type, &result);
    715    } else {
    716      // Nope: grab as an integer, and convert to a float.
    717      long num;
    718      if (CFNumberGetValue((CFNumberRef)ref, kCFNumberLongType, &num)) {
    719        result = num;
    720        *success = YES;
    721      } else {
    722        *success = NO;
    723      }
    724    }
    725    // Or is it a string (e.g. set by the command line "defaults" command)?
    726  } else if (typeID == CFStringGetTypeID()) {
    727    result = (float)CFStringGetDoubleValue((CFStringRef)ref);
    728    *success = YES;
    729  } else {
    730    // Can't convert to a number: fail.
    731    *success = NO;
    732  }
    733  CFRelease(ref);
    734  return result;
    735 }
    736 
    737 // Writes a named floating point value to the stored preferences.
    738 static void prefFloatWrite(NSString* prefName, float prefValue) {
    739  CFNumberRef cfFloat =
    740      CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &prefValue);
    741  CFPreferencesSetAppValue((CFStringRef)prefName, cfFloat, APP_ID);
    742  CFRelease(cfFloat);
    743 }
    744 
    745 // Reads a named integer value from the stored preferences.
    746 static int prefIntRead(NSString* prefName, BOOL* success) {
    747  Boolean internalSuccess;
    748  CFIndex result = CFPreferencesGetAppIntegerValue((CFStringRef)prefName,
    749                                                   APP_ID, &internalSuccess);
    750  *success = internalSuccess;
    751 
    752  return result;
    753 }
    754 
    755 // Writes a named integer value to the stored preferences.
    756 static void prefIntWrite(NSString* prefName, int prefValue) {
    757  CFPreferencesSetAppValue((CFStringRef)prefName,
    758                           (CFNumberRef)[NSNumber numberWithInt:prefValue],
    759                           APP_ID);
    760 }
    761 
    762 // Deletes the named preference values.
    763 static void prefDelete(NSString* prefName) {
    764  CFPreferencesSetAppValue((CFStringRef)prefName, NULL, APP_ID);
    765 }
    766 
    767 // Synchronizes the local preferences with the stored preferences.
    768 static void prefSynchronize(void) { CFPreferencesAppSynchronize(APP_ID); }
    769 
    770 // Internal version of accelGetData, with logging
    771 int getData(sms_acceleration* accel, int calibrated, id logObject,
    772            SEL logSelector) {
    773  IOItemCount iSize = recordSize;
    774  IOByteCount oSize = recordSize;
    775  kern_return_t result;
    776 
    777  if (running == NO) {
    778    return -1;
    779  }
    780 
    781  memset(iRecord, 1, iSize);
    782  memset(oRecord, 0, oSize);
    783 
    784  LOG_2ARG(@"    Querying device (%u, %d): ", sensors[sensorNum].function,
    785           sensors[sensorNum].recordSize);
    786 
    787 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
    788  const size_t InStructSize = recordSize;
    789  size_t OutStructSize = recordSize;
    790  result = IOConnectCallStructMethod(connection,
    791                                     function,  // magic kernel function number
    792                                     (const void*)iRecord, InStructSize,
    793                                     (void*)oRecord, &OutStructSize);
    794 #else   // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
    795  result = IOConnectMethodStructureIStructureO(
    796      connection,
    797      function,  // magic kernel function number
    798      iSize, &oSize, iRecord, oRecord);
    799 #endif  // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
    800 
    801  if (result != KERN_SUCCESS) {
    802    LOG(@"failed.\n");
    803    running = NO;
    804    return result;
    805  } else {
    806    LOG(@"succeeded.\n");
    807 
    808    accel->x = getAxis(0, calibrated);
    809    accel->y = getAxis(1, calibrated);
    810    accel->z = getAxis(2, calibrated);
    811    return 0;
    812  }
    813 }
    814 
    815 // Given the returned record, extracts the value of the given axis. If
    816 // calibrated, then zero G is 0.0, and one G is 1.0.
    817 float getAxis(int which, int calibrated) {
    818  // Get various values (to make code cleaner)
    819  int indx = sensors[sensorNum].axes[which].index;
    820  int size = sensors[sensorNum].axes[which].size;
    821  float zerog = zeros[which];
    822  float oneg = onegs[which];
    823  // Storage for value to be returned
    824  int value = 0;
    825 
    826  // Although the values in the returned record should have the proper
    827  // endianness, we still have to get it into the proper end of value.
    828 #if (BYTE_ORDER == BIG_ENDIAN)
    829  // On PowerPC processors
    830  memcpy(((char*)&value) + (sizeof(int) - size), &oRecord[indx], size);
    831 #endif
    832 #if (BYTE_ORDER == LITTLE_ENDIAN)
    833  // On Intel processors
    834  memcpy(&value, &oRecord[indx], size);
    835 #endif
    836 
    837  value = signExtend(value, size);
    838 
    839  if (calibrated) {
    840    // Scale and shift for zero.
    841    return ((float)(value - zerog)) / oneg;
    842  } else {
    843    return value;
    844  }
    845 }
    846 
    847 // Extends the sign, given the length of the value.
    848 int signExtend(int value, int size) {
    849  // Extend sign
    850  switch (size) {
    851    case 1:
    852      if (value & 0x00000080) value |= 0xffffff00;
    853      break;
    854    case 2:
    855      if (value & 0x00008000) value |= 0xffff0000;
    856      break;
    857    case 3:
    858      if (value & 0x00800000) value |= 0xff000000;
    859      break;
    860  }
    861  return value;
    862 }
    863 
    864 // Returns the model name of the computer (e.g. "MacBookPro1,1")
    865 NSString* getModelName(void) {
    866  char model[32];
    867  size_t len = sizeof(model);
    868  int name[2] = {CTL_HW, HW_MODEL};
    869  NSString* result;
    870 
    871  if (sysctl(name, 2, &model, &len, NULL, 0) == 0) {
    872    result = [NSString stringWithFormat:@"%s", model];
    873  } else {
    874    result = @"";
    875  }
    876 
    877  return result;
    878 }
    879 
    880 // Returns the current OS X version and build (e.g. "10.4.7 (build 8J2135a)")
    881 NSString* getOSVersion(void) {
    882  NSDictionary* dict =
    883      [NSDictionary dictionaryWithContentsOfFile:
    884                        @"/System/Library/CoreServices/SystemVersion.plist"];
    885  NSString* versionString = [dict objectForKey:@"ProductVersion"];
    886  NSString* buildString = [dict objectForKey:@"ProductBuildVersion"];
    887  NSString* wholeString =
    888      [NSString stringWithFormat:@"%@ (build %@)", versionString, buildString];
    889  return wholeString;
    890 }
    891 
    892 // Returns time within the current second in microseconds.
    893 // long getMicroseconds() {
    894 //	struct timeval t;
    895 //	gettimeofday(&t, 0);
    896 //	return t.tv_usec;
    897 //}
    898 
    899 // Returns fake data given the time. Range is +/-1.
    900 float fakeData(NSTimeInterval time) {
    901  long secs = lround(floor(time));
    902  int secsMod3 = secs % 3;
    903  double angle = time * 10 * M_PI * 2;
    904  double mag = exp(-(time - (secs - secsMod3)) * 2);
    905  return sin(angle) * mag;
    906 }