Monday, March 19, 2007

Bluetooth Proximity Detection on OS X

DroppedimageOne thing that I've been playing with off and on for some time is a small efficient little solution for handling basic Bluetooth proximity detection, specifically for being able to perform certain actions when a cell phone or other Bluetooth device is in range of my Powerbook.

As an IT Consultant, I am frequently working in various locations at different clients' sites, and it's nice to have my Powerbook secure itself when I'm away from the machine. In addition, my other objectives are to keep the OS X Address Book application connected and to iSync my phone whenever it moves back within proximity of my machine.

Ideally, I would want to activate the OS X screen saver and enable the password protection when I move away from my computer (out of Bluetooth range), but otherwise I'd prefer to keep the screen saver password off for normal use, as it gets quite annoying when I'm working near the computer to have to continually re-enter my password after I've diverted my attention elsewhere for a few minutes (which happens frequently, as often the Powerbook sits to one side of other systems that I'm working with, rather than being in constant use).

Presently, the only software solution that will actually handle this with any kind of transparency from a security point of view is a tool called Home Zone that has been only recently released in beta form. While Home Zone looks like an excellent package to keep an eye on, it's fairly new and may also be a bit more complex than the requirements of a basic Bluetooth proximity detection system. One neat feature, however, is that it also adds WiFi detection to the mix. Home Zone also includes pre-defined actions to do things like Enabling and Disabling the screen saver password, a task that is otherwise more difficult to accomplish than one might expect.

Unfortunately, as of Beta 7, I had little success getting it to reliably detect the presence of a Bluetooth device even in the simplest configuration, and it became frustrating to have my screensaver kick in on me while I was working on the computer only because Home Zone had lost track of the Bluetooth device.

Another excellent tool that will handle proximity detection as part of its much more robust suite of features is Salling Clicker. This is an absolutely outstanding application, but again one that goes well beyond basic proximity detection. Further, since Salling Clicker is really just AppleScript-based in it's operations, the basic solution and scripts that I describe below can easily be adapted into that as well (in fact, even though I'm a licensed user and big fan of Salling Clicker, the only reason I'm not using it for this purpose today is that proximity detection is not yet supported with the Nokia E62 that I use).

The tool I ultimately chose for the purpose of the detection itself is a little free app appropriately called Proximity. This is a thin little program that does one thing, but does it well -- that is to sit in the background and scan for a given Bluetooth device at regular intervals. When it detects a change in the device's availability, it simply calls one of two Applescripts: One for the device leaving range, and another for when the device enters range.

So, armed with that I set out to create two Applescripts that would perform the following tasks:

When the Bluetooth Device enters range:
  • Deactivate the Screen Saver Password.
  • Deactivate the Screen Saver.
  • Reconnect the phone to the OS X Address Book
  • Sync the phone using iSync
When the Bluetooth Device leaves range:
  • Activate the Screen Saver Password.
  • Activate the Screen Saver.
Activating the screen saver and performing an iSync are both tasks that are trivial to perform via Applescript. Reconnecting the Address Book and enabling and disabling the screen saver password protection is considerably more complicated, however, as I quickly discovered.

I should point out that most of what I am documenting here has been gleaned from various corners of the web, and therefore most of the ideas are not specifically my own. However, I decided to try and document some of this in one place in order to hopefully save others the several hours of searching that it took me to put it all together.

Activating and Deactivating the Screen Saver

Activating and Deactivating the screen saver itself is trivial to do through Applescript simply through the use of the following two Applescript commands:

Activating the Screen Saver:
tell application "ScreenSaverEngine" to activate
Deactivating the Screen Saver:
tell application "ScreenSaverEngine" to quit

Performing an iSync

Likewise, once iSync itself has been properly configured for your phone, performing an iSync is not much more complicated. A basic sync is performed with the following command:
tell application "iSync" to synchronize
However, for my own purposes, I chose to expand upon this. Specifically, I decided there was no point in having iSync automatically sync more often than every 15 minutes or so. This prevents it from kicking in every time I happen to wander away from the computer and back. The following script will only tell iSync to synchronize if a sync has not occurred in the last 900 seconds (15 minutes):
tell application "iSync" if last sync is less than ((current date) - 900) then synchronize end if end tell
Further, since the iSync window will otherwise tend to come up and get in the way when this happens, I prefer to keep it hidden with the following additional command:
tell application "System Events" to set visible of process "iSync" to false
This will have the effect of running iSync if the phone has not been synced in the last 15 minutes, and immediately hiding the iSync window from view. The iSync will continue to run in the background until it completes.

Reconnecting to the Address Book

Although Bluetooth support in the OS X Address Book is a very cool feature, the reality is that it has been poorly implemented up to this point in terms of it's ability to stay connected to a Bluetooth phone, or even in terms of making this process scriptable via Applescript.

Fortunately, this was discussed some time ago in the Salling Clicker forums and incorporated into the proximity scripts that are included with Salling Clicker. The solution, it would seem, is to toggle an internal Address Book preference to force it look for its associated Bluetooth device the next time it starts up, and then just shut down the Address Book app and restart it. This is accomplished with the following snippet of code, which is a simplification of code pulled from the "Keep Address Book Connected" script included with Salling Clicker
tell application "Address Book"
     if not unsaved then
               delay 1
          end try
     end if
end tell

do shell script "defaults write ABCheckForPhoneNextTime -boolean true"

     tell application "Address Book" to launch

     tell application "System Events"
          set the visible of process "Address Book" to no
     end tell

end try
Placed within the script that executes when entering proximity, this will toggle the option to find a Bluetooth device ON in the Address Book preferences, and then shut down and restart the Address Book app. It's messy, but it does work.

(I'm sure I'm not alone in hoping that Apple makes this function accessible through Applescript in Leopard).

The Final Challenge: Enabling and Disabling the Screen Saver Password

The final hurdle in this process was programmatically changing the password protection on the OS X screen saver. While this is handled very elegantly by the Home Zone application that I mentioned at the beginning of this article, I wanted to find a way to do it programmatically through Applescript myself for various reasons. It turns out this process was slightly more complex than I had initially suspected.

Firstly, the preference that determines whether the OS X screen saver asks for a password is stored within each user's local preferences, specifically in the domain. The preference file itself is a little tricky, as it is named based on a host ID. Fortunately, it can be accessed using the built-in "defaults" command-line tool. The specific key is "askForPassword" and contains an integer value of zero or one to determine whether the screensaver prompts for a password or not.

The following command, executed in terminal or from a "do shell script" within Applescript, will set this value to enable password protection on the screen saver:
defaults -currentHost write askForPassword -int 1
This is well-documented in several places on the Internet, although there a couple of important things that should be noted.

Firstly, it is necessary to specify the "-int" parameter. Without it, the "defaults" command will write the value as a string value, which will be treated as an "OFF" setting regardless of the content. More specifically, anything in that key other than an integer 1 will disable the screen saver password.

More importantly, however, this setting does not take effect immediately.... Either the System Preferences application must be opened and other changes made, or the user must log out and back in. This is because the password requirement is only read by the "loginwindow" process.

By default, changes in the "System Preferences" app in OS X will send a notification to the loginwindow process to re-read these settings. However, to change the setting programmatically and have it take effect immediately, it's necessary to find an alternative way to refresh the loginwindow process.

After much digging, the solution was found in a thread on the macosxhints forum, in the last post by Guillaime O. Specifically, Guilaime provides a snippet of C code that can be quickly and easily compiled into an executable to perform this specific function.
#include <CoreFoundation/CoreFoundation.h>
int main(int argc, char ** argv) 
     CFMessagePortRef port = CFMessagePortCreateRemote(NULL, CFSTR("")); 
     CFMessagePortSendRequest(port, 500, 0, 0, 0, 0, 0);
     return 0;
This code can simply be compiled with the built-in C compiler on OS X (if you have the Development Tools installed), and then simply put somewhere in the path. Then, immediately after running the "defaults" command to set the screen saver password state, simply call this application to refresh the "loginwindow" process and re-read the "askForPassword" setting.

For my own purposes, I just went with the suggested name of "notif" for the executable, but it can of course be named anything you like.

The Final Result

So, after all is said and done, the final result is the following two scripts:

Entering Proximity.scpt

     -- Disable the screen Saver Password

     do shell script "defaults -currentHost write askForPassword -int 0"

     do shell script "notif"

     -- Turn OFF the screen saver

     tell application "ScreenSaverEngine" to quit

     tell application "Address Book"
          if not unsaved then
                    delay 1
               end try
          end if
     end tell

     -- Reconnect to the Address Book

     do shell script "defaults write ABCheckForPhoneNextTime -boolean true"

          tell application "Address Book"
          end tell

          tell application "System Events"
               set the visible of process "Address Book" to no
          end tell
     end try

     -- Synchronize the Device

     tell application "iSync"
          if last sync is less than ((current date) - 900) then
          end if
     end tell

     tell application "System Events" to set visible of process "iSync" to false

Leaving Proximity.scpt
-- Turn on the screen saver password
do shell script "defaults -currentHost write askForPassword -int 1"
do shell script "notif"
-- Activate the screen saver
tell application "ScreenSaverEngine" to activate

Other Possible Tricks

One other approach I had tried was to make use of the "CGSession" command to do a lock by returning to the actual login screen (effectively a fast-user-switching feature that presents the login screen without logging the user out). While this was a very neat solution, it lacked the intuitive "unlock" feature, since once returning to the login screen, there was really no way to get back in without a password (at least not a simple method that I have yet discovered without embedding my password somewhere in the file).

However, for those interested, the following Applescript entry will accomplish this task:

do shell script "/System/Library/CoreServices/
Menu\\ Extras/ -suspend"

This works reasonably well, and the entering proximity script will even run in the background, so the only disadvantage is that you are forced to log in manually when you return to the computer, and there is a small delay in this process.

Once thing I was able to do with this, however, was to combine it with another tool, the excellent SleepWatcher daemon, to allow me to run shell scripts when the computer wakes or sleeps. I simply instruct SleepWatcher to run a script including the above command with a slight delay to allow it to complete, and then whenever the computer goes to sleep, my session is returned to the log in screen.

While the Bluetooth proximity detection feature will also address this (if the computer is awakened without the necessary Bluetooth device nearby), this option is slightly more secure, and allows for the ability for somebody else to log onto the computer if necessary (a screen saver password would restrict access to the currently logged in user only).

References & Acknowledgements

Again, most of what is discussed in here has been gleaned and put together from information in various places on the Internet. Specifically, the following should be acknowledged:

Proximity 1.0A very simple and effective free Bluetooth Proximity detection tool for Mac OS X
Home Zone beta 7A very slick up-and-coming solution by Jonas Witt to set parameters based on "Zones" which are in turn based upon WiFi and Bluetooth proximity.
Salling Clicker 3.0.1Jonas Salling's absolutely outstanding Bluetooth remote control and proximity detection app, and the source for the Address Book reconnect script included above.
SleepWatcher 2.0.4  A neat little daemon by Bernhard Baehr to monitor and execute shell scripts based on sleep and wake events.
Mac OS X HintsMost of the solutions and script snippets regarding the screen saver password protection came from the Mac OS X Hints forum. Specifically, the simple but indispensable C code for the "notif" application was contributed to these forums in a post by Guillame O.