Managing Mouse Button Behavior in Modern macOS

blinvisible
Contributor

During a computer lab upgrade from OS X Mavericks to macOS Sierra, for which I went through the process of migrating managed preferences (MCX) to Config Profiles, I discovered that mouse button behavior wasn't being applied. Buttons were defined in a custom config profile, and if you looked at the Mouse System Preferences pane it would show all the correct settings, but all the buttons still behaved as "Primary Button" or left-click. Running defaults read or restarting cfprefsd seemed to make no difference.

Then I noticed that if you merely clicked on one of the button settings in the Mouse pref pane, even without changing its value, the desired button behavior would suddenly start working. After some reverse engineering it was revealed that updating the user preferences cache wasn't enough, the kernel preferences cache also needed to be updated -- something the config profile on its own wasn't doing. Running this script as a LaunchAgent fixed the problem for our environment.

Note that this specifically addresses the preferences cache for the Apple wired Mighty Mouse ("com.apple.driver.AppleHIDMouse"); if you use the wireless Magic Mouse or Magic Trackpad, that uses at minimum a different preference domain and possibly also other button labels (I don't have one to test myself). Modify as needed.

#!/usr/bin/python
import CoreFoundation
from Foundation import NSBundle
import objc

# Set Button 1 aka "Primary Button"
CoreFoundation.CFPreferencesSetAppValue("Button1", 1,  "com.apple.driver.AppleHIDMouse") 
# Set Button 2 aka "Secondary Button"
CoreFoundation.CFPreferencesSetAppValue("Button2", 2,  "com.apple.driver.AppleHIDMouse") 
# Set Button 3
CoreFoundation.CFPreferencesSetAppValue("Button3", 3,  "com.apple.driver.AppleHIDMouse") 
# Set Button 4
CoreFoundation.CFPreferencesSetAppValue("Button4", 4,  "com.apple.driver.AppleHIDMouse") 

# Update user preferences cache
CoreFoundation.CFPreferencesAppSynchronize("com.apple.driver.AppleHIDMouse")

# Update kernel preferences cache
bezel_bundle = NSBundle.bundleWithPath_('/System/Library/PrivateFrameworks/BezelServices.framework')
functions = [('BSKernelPreferenceChanged', '@@'),]
objc.loadBundleFunctions(bezel_bundle, globals(), functions)
BSKernelPreferenceChanged("com.apple.driver.AppleHIDMouse")

If you are trying to get right-click or other multi-button behavior functioning on your managed Macs and are having no luck with config profiles or other previously-successful methods, give the above script a try and let us know how it goes!

Anyone have any other methods for reliably setting mouse button behavior in modern managed macOS?

References/Acknowledgements:
- BezelServices on OS X by Robert Sesek
- Reverse Engineering the OS: A Practical Guide by Michael Lynn

7 REPLIES 7

dderusha
Contributor

This should work for a Apple Bluetooth mouse

#!/usr/bin/python
import CoreFoundation
from Foundation import NSBundle
import objc

# Set Secondary Click
CoreFoundation.CFPreferencesSetAppValue("MouseButtonMode", "TwoButton",  "com.apple.driver.AppleBluetoothMultitouch.mouse")
# Update user preferences cache
CoreFoundation.CFPreferencesAppSynchronize("com.apple.driver.AppleBluetoothMultitouch.mouse")

# Update kernel preferences cache
bezel_bundle = NSBundle.bundleWithPath_('/System/Library/PrivateFrameworks/BezelServices.framework')
functions = [('BSKernelPreferenceChanged', '@@'),]
objc.loadBundleFunctions(bezel_bundle, globals(), functions)
BSKernelPreferenceChanged("com.apple.driver.AppleBluetoothMultitouch.mouse")

lynnp
New Contributor

this segfaults for me. Not sure if it has to do with python version? Here's me running each line by hand. It segfaults on the final line:

sh-3.2# python
Python 2.7.11 (default, Nov  9 2016, 11:36:34)
[GCC 4.2.1 Compatible Apple LLVM 7.3.0 (clang-703.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import CoreFoundation
>>> from Foundation import NSBundle
>>> import objc
>>> CoreFoundation.CFPreferencesSetAppValue("MouseButtonMode", "TwoButton",  "com.apple.driver.AppleBluetoothMultitouch.mouse")
>>> CoreFoundation.CFPreferencesAppSynchronize("com.apple.driver.AppleBluetoothMultitouch.mouse")
True
>>> bezel_bundle = NSBundle.bundleWithPath_('/System/Library/PrivateFrameworks/BezelServices.framework')
>>> functions = [('BSKernelPreferenceChanged', '@@'),]
>>> objc.loadBundleFunctions(bezel_bundle, globals(), functions)
>>> BSKernelPreferenceChanged("com.apple.driver.AppleBluetoothMultitouch.mouse")
Segmentation fault: 11
sh-3.2#

blinvisible
Contributor

@lynnp Are you copying/pasting those commands? I can recreate a "Segmentation fault: 11" error if I copy the BSKernelPreferenceChanged line and what I've highlighted includes includes anything after the closing parenthesis, like maybe an unseen tab/newline/carriage return character at the end. If I type the command out, or highlight to copy and ensure the selection ends at the closing parenthesis, it seems to still work in my testing.

lynnp
New Contributor

@blinvisible Even after typing out the whole thing by hand I still get the same segfault. I made sure there weren't any hanging chars or weird encoding. :/

blinvisible
Contributor

@lynnp On what OS are you running the script when it fails? I've had success on 10.12.x through 10.13.x, although I'm using the AppleHIDMouse preference and not the AppleBluetoothMultitouch one (I don't have a wireless mouse to test). I'm also using the stock install of Python 2.7.10 on each OS.

z3ky
New Contributor

Get segmentation fault on Big Sur 11.6 but not for every execution of the script. sometimes it works without any error.

OSAITMacAdmin
New Contributor

Hi 

Anyone able to get this script working on Monterey v12.4. Getting the error 

NameError: name 'BSKernelPreferenceChanged' is not defined on the last line of the script.