hammerspoon icon indicating copy to clipboard operation
hammerspoon copied to clipboard

How do I enable/disable sticky keys programmatically?

Open NightMachinery opened this issue 1 year ago • 1 comments

I want to disable sticky keys on certain apps. To do so, I need a way to programmatically toggle the sticky keys settings. Is there a way to do this?

Here is the setting in the macOS GUI, for reference: enter image description here

Related:

  • https://apple.stackexchange.com/questions/468312/how-do-i-enable-disable-sticky-keys-programmatically

  • https://apple.stackexchange.com/questions/81523/disable-sticky-keys-only-while-certain-applications-have-focus

NightMachinery avatar Jan 13 '24 08:01 NightMachinery

Pressing shift for five times quickly toggles this setting. I wonder if doing that via Hammerspoon would work. How can I press the shift key alone?

NightMachinery avatar Jan 13 '24 09:01 NightMachinery

hs.eventtap can emit keystrokes of your choice, so you could try that, but macOS may not consider it for the accessibility shortcut.

cmsj avatar Aug 06 '24 12:08 cmsj

I tried doing this (with various delays between events), but the system didn't seem to pick it up:

for i=1,5 do
	hs.eventtap.event.newKeyEvent(hs.keycodes.map.shift, true):post()
	hs.timer.usleep(1000)
	hs.eventtap.event.newKeyEvent(hs.keycodes.map.shift, false):post()
	hs.timer.usleep(1000)
end

Edit: I also tried:

for i=1,5 do
	local event = hs.eventtap.event.newEvent()
	event:setType(hs.eventtap.event.types.flagsChanged)
	event:setFlags{shift=true}:post()
	event = hs.eventtap.event.newEvent()
	event:setType(hs.eventtap.event.types.flagsChanged)
	event:setFlags{}:post()
end

Another option which does seem to work is to use hs.axuielement to click the checkbox for it in System Preferences. Here's an example of how to do that, adapted from some code from my config that toggles Mouse Keys:

local function shellArg(x)
	return [[']] .. x:gsub([[']], [['\'']]) .. [[']]
end

local function stickyKeysAreOn()
	return (hs.execute[[defaults read com.apple.universalaccess stickyKey]]):match('(%d+)') == '1'
end

local function setStickyKeys(enabled)
	if stickyKeysAreOn() == enabled then return end
	hs.execute('osascript -e '..shellArg[[
		tell app id "com.apple.systempreferences"
			reveal anchor "Keyboard" of pane id "com.apple.preference.universalaccess"
		end
	]])
	hs.axuielement.applicationElement(hs.application.get 'com.apple.systempreferences'):elementSearch(function(msg, results)
		local cb = results[1]
		if (cb.AXValue == 1) ~= enabled then
			cb:doAXPress()
		end
	end, hs.axuielement.searchCriteriaFunction{
		attribute = "AXIdentifier",
		value = "UA_StickyKeys_Checkbox"
	}, {count = 1, depth = 4})
end

-- example:
setStickyKeys(true)
Original version for Mouse Keys
local function shellArg(x)
	return [[']] .. x:gsub([[']], [['\'']]) .. [[']]
end

local function mouseKeysAreOn()
	return (hs.execute[[defaults read com.apple.universalaccess mouseDriver]]):match('(%d+)') == '1'
end

local function setMouseKeys(enabled)
	if mouseKeysAreOn() == enabled then return end
	hs.execute('osascript -e '..shellArg[[
		tell app id "com.apple.systempreferences"
			reveal anchor "Alternate_Pointer_Actions" of pane id "com.apple.preference.universalaccess"
		end
	]])
	hs.axuielement.applicationElement(hs.application.get 'com.apple.systempreferences'):elementSearch(function(msg, results)
		local cb = results[1]
		if (cb.AXValue == 1) ~= enabled then
			cb:doAXPress()
		end
	end, hs.axuielement.searchCriteriaFunction{
		attribute = "AXTitle",
		value = "Enable Mouse Keys" -- depends on the selected language
	}, {count = 1, depth = 4})
end

-- example:
setMouseKeys(true)

(I'm hs.executeing osascript instead of just using hs.osascript.applescript in order to avoid #1980.)

Rhys-T avatar Aug 06 '24 14:08 Rhys-T