Skip to content

Instantly share code, notes, and snippets.

@nriley
Last active March 6, 2024 17:02
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save nriley/f2dfb2955836462b8f7806ce0da76bfb to your computer and use it in GitHub Desktop.
Save nriley/f2dfb2955836462b8f7806ce0da76bfb to your computer and use it in GitHub Desktop.
Hammerspoon script for ensuring Sidecar is active when iPad is plugged in (macOS 10.15; see comments for versions up to macOS 13)
hs.loadSpoon('SpoonInstall')
spoon.SpoonInstall.use_syncinstall = true
Install = spoon.SpoonInstall
log = hs.logger.new('init', 5)
-- function debugUI(msg, table)
-- log:d(msg)
-- log:d(hs.inspect(table))
-- end
function connectToSidecar(msg, results, count)
local item
sidecarItemFound = (count > 0)
if not sidecarItemFound then
return log:d("Can't get Sidecar connection menu item:", msg)
end
item = results[1]
if item.AXMenuItemMarkChar == nil then
-- log:d("Connecting to Sidecar...")
item:doAXPress()
else
-- log:d("Closing menu - already connected to Sidecar...")
item.AXParent:doAXCancel()
end
end
function stopConnectToSidecarItemSearchTimer()
if connectToSidecarItemSearchTimer then
connectToSidecarItemSearchTimer:stop()
connectToSidecarItemSearchTimer = nil
end
end
function displaysMenu(msg, results, count)
local menu, connectToSneezerItemSearch
if count == 0 then
return log:d("Can't get displays menu:", msg)
end
menu = results[1]
menu:doAXPress()
connectToSneezerItemSearch = hs.axuielement.searchCriteriaFunction({
{attribute = 'AXRole', value = 'AXMenuItem'},
{attribute = 'AXIdentifier', value = '_deviceSelected:'},
{attribute = 'AXTitle', value = 'Sneezer'}})
-- menu:elementSearch(debugUI, connectToSneezer,
-- {objectOnly = false, asTree = true, depth = 2})
-- iPad may not appear immediately in the Displays menu
-- wait up to 3 seconds for it to appear
stopConnectToSidecarItemSearchTimer()
sidecarItemFound = false
connectToSidecarItemSearchTimer = hs.timer.doUntil(
function()
return sidecarItemFound
end,
function()
menu:elementSearch(connectToSidecar, connectToSneezerItemSearch,
{count = 1, depth = 2, noCallback = true})
end,
0.5)
hs.timer.doAfter(3, stopConnectToSidecarItemSearchTimer)
end
function connectSidecar()
local suisApp, suisAX, displayMenuSearch
suisApp = hs.application.find('com.apple.systemuiserver')
suisAX = hs.axuielement.applicationElement(suisApp)
displayMenuSearch = hs.axuielement.searchCriteriaFunction({
{attribute = 'AXRole', value = 'AXMenuBarItem'},
{attribute = 'AXSubrole', value = 'AXMenuExtra'},
{attribute = 'AXDescription', value = '^Displays', pattern = true}
})
-- suisAX:elementSearch(debugUI, displayMenuSearch,
-- {objectOnly = false, asTree = true, depth = 2})
suisAX:elementSearch(displaysMenu, displayMenuSearch,
{count = 1, depth = 2})
end
-- what we actually get when the iPad connects:
-- connect, disconnect, connect
-- so, don't trigger twice within half a second
connectSidecarTimer = hs.timer.delayed.new(0.5, function()
connectSidecar()
end)
function iPadConnected(connected)
-- log:d("iPad connected?", connected)
if connected then
connectSidecarTimer:start()
else
connectSidecarTimer:stop()
end
end
Install:andUse(
"USBDeviceActions",
{
config = {
devices = {
iPad = { fn = iPadConnected }
}
},
start = true
}
)
@nriley
Copy link
Author

nriley commented Mar 5, 2024

@peterhartree Sorry, I don't have macOS 14 easily accessible to test. You can try using Accessibility Inspector if you want to figure out what changed since the prior version, but instead of scripting System Settings you could also try https://github.com/Ocasio-J/SidecarLauncher — just read about it today. It uses a private API to enable/disable Sidecar and is tested on macOS 14.

@peterhartree
Copy link

Thank you @nriley. SidecarLauncher is working perfectly on Mac OS 14.2.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment