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.
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.
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 “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:
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.
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.
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 Type
s 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;
}
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.