Overview

BezelServices is a subsystem on Mac OS X that acts as a glue layer between HID device drivers in the kernel, preferences that influence the behavior of those devices stored in CFPreferences, and a small UI surface that provides feedback about the state of the devices. Examples of devices that make use of BezelServices are MagicTrackpad, Bluetooth keyboards, volume and screen brightness controls, and IR remotes.

Introduction

I use a Mac Mini connected to my TV and an Apple Remote to control it. For some reason, on OS X 10.11 the IR remote periodically stops working. The only way to fix it (besides rebooting) is to toggle the checkbox at System Preferences > Security > Advanced > Disable remote control infrared receiver. Since SSH is far more convenient than using VNC and a GUI, I decided to write a small tool that would perform this same operation. In this essay, I will use the Apple IR Controller as an example to explain what BezelServices is and how it works.

Architecture

BezelServices provides a typical Mach client-server subsystem. The server is hosted within the loginwindow process at the Mach endpoint named “com.apple.BezelServices”. The framework is loaded into loginwindow as a plugin, located at /System/Library/LoginPlugins/BezelServices.loginPlugin. Clients, like the System Preferences application, connect to it using the API in /System/Library/PrivateFrameworks/BezelServices.framework. The BezelServices server receives Mach MIG messages from clients and communicates the resulting commands to the HID driver via IOKit.

The UI elements displayed in response to BezelServices events run in a separate process at /System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/BezelUI/BezelUIServer. This process runs another Mach MIG server at the endpoint named “com.apple.BezelUI”. The server’s only responsibility is to display Unicode text and graphics for the on-screen display elements (as shown in the screenshot above). Clients can send messages to the BezelUIServer directly using the framework, or indirectly via the BezelServices loginwindow server as the result of a message to that server.

The Apple IR Controller

The “Disable remote control infrared receiver” System Preference is a simple example to demonstrate the BezelServices architecture. When this checkbox is toggled, System Preferences performs two operations:

  1. Updating the value of the preference in CFPreferences (and synchronizing the value to disk).
  2. Communicating the new value to the kernel HID driver.

The value needs to be set in both places, since the hardware will reset to its intrinsic value on boot. When loginwindow starts, it reads the stored preferences for the device and communicates them to the driver, overriding the intrinsic value with the last-used one.

The Security preference pane performs step one by indirectly setting the CFPreference value using the /System/Library/PrivateFrameworks/SystemAdministration.framework. This framework allows writing root (/Library) preferences with the help of an XPC service. This service called writeconfig uses the AuthorizationRef granted from unlocking the System Preference pane to get root privileges.

To perform step two, System Preferences must notify BezelServices that the value has been changed. It calls BSKernelPreferenceChanged() in the framework, which sends a message to the BezelServices server with the new domain-key-value CFPreferences tuple. When the server receives the message, it sends the value to all HID devices that have registered for changes on that preference key.

Emulating the System Preference

In order to manipulate the “Disable remote control infrared receiver” setting at the command line, I wrote a small tool, apple-ir-control that can query or modify the preference. It uses typical CFPreferences API calls to set and synchronize the value, and then IORegistryEntrySetCFProperty() to push the value to the kernel HID driver.

BezelServices Plugins

As stated above, BezelServices acts as a glue layer between a HID device, user preferences, and feedback UI elements. The devices that BezelServices supports are dynamically selected when the server starts. They are configured via bundles in /Library/Application Support/Apple/BezelServices.

Each bundle’s Info.plist has a BezelServicesPlugin key that maps to a dictionary. This dictionary contains another dictionary under the key Classes. This maps an IOService class of that name to a set of PreferenceDefaults, Messages, or Actions that BezelServices will handle for the plugin.

PreferenceDefaults are the default values for preferences of the HID device. These defaults keys are used to map BSKernelPreferenceChanged() messages to the device that should receive the updated value. They also are the keys of the CFPreference values that are stored on disk. In addition, the Classes dictionary specifies the PreferenceHost, PreferenceIdentifier, and PreferenceUser under which the preferences should be saved by CFPreferences. The server communicates the new value to the kernel HID device by constructing a CFDicictionary with the keys and values of the current preferences and calling IORegistryEntrySetCFProperties() on the service it mached when the plugin loaded.

The Messages dictionary under Classes corresponds to named messages sent from the HID device to the BezelServices server. The messages are received using the IOServiceAddInterestNotification() callback mechanism. When BezelServices loads a plugin, it matches the IOService from IOKit and installs a notification for that service. The messages are sent from the kernel using IOService::messageClients(). The messageType argument is always the magic number 0x62736b32, which breaks down to the Mach error code: <0x18|0x9cd|0x2b32>. Neither the Mach error system or subsystem are published. The messageArgument argument is a string message name, which matches the one declared in the plist dictionary.

Upon receipt of a registered message, the action specified by the Action key in the Messages dictionary is taken. This Action name matches one in the Actions dictionary in the Classes dictionary. Currently, there are two Types of action that are in use: OSD (presumably “on-screen display”) and NotificationCenter to publish a notification using NSUserNotification.

Below is a trimmed sample of the AppleBluetoothHIDMouse.plugin plist, to show the structure of a BezelServices plugin. As an example, when the HID device sends a “MouseConnected” message, the “MouseConnectOSD” action is taken; this shows the bezel UI with a “BtMouse.pdf” graphic and a localized string named “MouseConnectText”. This device also receives changed values for a few preferences.

{
    BundleIdentifier = "com.apple.driver.AppleBluetoothHIDMouse";
    Classes =     {
        AppleBluetoothHIDMouse =         {
            Actions =             {
                MouseConnectOSD =                 {
                    DurationMS = 2000;
                    Image = "BtMouse.pdf";
                    MessageKey = MouseConnectedText;
                    Priority = 600;
                    Type = OSD;
                };
                MouseCriticallyLowBatteryNC =                 {
                    NCHelpButtonText = HelpButtonText;
                    NCImageURL = "Mouse.icns";
                    NCInformativeText = MouseDangerouslyLowText;
                    NCPrefPaneDeviceEra = 2007Mouse;
                    NCPrefPaneDeviceType = Mouse;
                    NCTitle = MouseDangerouslyLowHeader;
                    Type = NotificationCenter;
                };
            };
            Messages =             {
                MouseConnected =                 {
                    Action = MouseConnectOSD;
                    CancelsPendingAction = MouseDisconnectOSD;
                    CancelsSelf = 1;
                    SuppressConnectionActions = 1;
                };
                MouseCriticallyLowBattery =                 {
                    Action = MouseCriticallyLowBatteryNC;
                    SuppressBatteryActions = 1;
                };
            };
            PreferenceDefaults =             {
                Button1 = 1;
                Button2 = 1;
                Button3 = 0;
                Button4 = 0;
            };
            PreferenceHost = Any;
            PreferenceIdentifier = "com.apple.driver.AppleHIDMouse";
            PreferenceUser = Current;
        };
    };
    Version = 2;
}

Conclusion

This essay gives an introduction to BezelServices on OS X and how its plugin architecture is used to coordinate between HID kernel drivers and userspace. While not covered here, the BezelServices server is also responsible for handling the volume adjustment, screen brightness, and keyboard backlight brightness elements. These device settings and UI elements are handled using dedicated, built-in components of the BezelServices server, instead of through plugins.