Has anyone made a script that lets you use autohide on the dock, except displays the dock while only on your desktop? #3741
Replies: 1 comment 1 reply
-
Not 100% sure what you mean by "on your desktop", but I'll assume you mean when the Finder is active for this... This caught my attention because this is one of those things that seems like it should be easy, but Apple has other ideas... It proved to be a bit annoying and required a bit more legerdemain than I expected... I'm not aware of a quick and easy way to do this, so I looked to At any rate, here is what I came up with, and it wasn't as simple or straightforward as you would think... and flashes a dock menu when changes are made... annoying but can't get around things like that when using accessibility elements. Save the following as something in your hammerspoon config directory (e.g. "dockHider.lua") local module = {}
local axuielement = require("hs.axuielement")
local eventtap = require("hs.eventtap")
local keycodes = require("hs.keycodes")
local application = require("hs.application")
local watcher = require("hs.application.watcher")
local fnutils = require("hs.fnutils")
local timer = require("hs.timer")
-- Not sure if these differ from language to language, so set here if you need to; if you
-- want to get really fancy and look at the localization strings, see the code for `hs.spaces`
-- at: https://github.com/Hammerspoon/hammerspoon/blob/master/extensions/spaces/spaces.lua
local onMenuItem = "Turn Hiding On"
local offMenuItem = "Turn Hiding Off"
-- app titles which should show the Dock; if it's not in here, the dock will be hidden
module.showDockIn = {
"Finder",
}
-- axuielement dock stuff
local dockState -- track internally when we switch to minimize spurious menu flashes
local getDock = function()
local dockElement = axuielement.applicationElement("com.apple.dock")
assert(dockElement, "Unable to aquire Dock accessibility element")
return dockElement
end
local getItemListFromDock = function()
local dockElement = getDock()
local axlist
for i,v in ipairs(dockElement) do
if v.AXRole == "AXList" and v.AXRoleDescription == "list" then
axlist = v.AXChildren
break
end
end
assert(axlist, "Unable to get child list from Dock")
return axlist
end
local getSeparator = function()
local axlist = getItemListFromDock()
for i,v in ipairs(axlist) do
if v.AXSubrole == "AXSeparatorDockItem" then return v end
end
return false
end
-- generally this should remain valid unless you restart the Dock, so we check in the
-- various functions, just to be sure...
local separator = getSeparator()
module.isHidingOn = function()
if not separator:isValid() then separator = getSeparator() end
separator:doAXShowMenu()
while not separator.AXShownMenuUIElement do end
local answer
for _, menuItem in ipairs(separator.AXShownMenuUIElement) do
if answer == nil then
if menuItem.AXTitle == onMenuItem then
answer = false
elseif menuItem.AXTitle == offMenuItem then
answer = true
end
end
end
-- hmm, macos has changed a bit; this no longer closes when already open
-- separator:doAXShowMenu()
eventtap.event.newKeyEvent({}, keycodes.map.escape, true):post()
eventtap.event.newKeyEvent({}, keycodes.map.escape, false):post()
if answer == nil then
error([[ unable to find Menu Item "Turn Hiding On" or "Turn Hiding Off" ]])
else
return answer
end
end
module.hideDock = function(skipShowMenu)
if not separator:isValid() then separator = getSeparator() end
if not skipShowMenu then separator:doAXShowMenu() end
local currentTime = timer.secondsSinceEpoch()
while not separator.AXShownMenuUIElement do
if timer.secondsSinceEpoch() - currentTime > 5 then
error("timeout waiting for AXShownMenuUIElement")
end
end
local answer
for _, menuItem in ipairs(separator.AXShownMenuUIElement) do
if menuItem.AXTitle == onMenuItem then
menuItem:doAXPress()
return
end
end
-- see isHidingOn
eventtap.event.newKeyEvent({}, keycodes.map.escape, true):post()
eventtap.event.newKeyEvent({}, keycodes.map.escape, false):post()
end
module.showDock = function(skipShowMenu)
if not separator:isValid() then separator = getSeparator() end
if not skipShowMenu then separator:doAXShowMenu() end
local currentTime = timer.secondsSinceEpoch()
while not separator.AXShownMenuUIElement do
if timer.secondsSinceEpoch() - currentTime > 5 then
error("timeout waiting for AXShownMenuUIElement")
end
end
local answer
for _, menuItem in ipairs(separator.AXShownMenuUIElement) do
if menuItem.AXTitle == offMenuItem then
menuItem:doAXPress()
return
end
end
-- see isHidingOn
eventtap.event.newKeyEvent({}, keycodes.map.escape, true):post()
eventtap.event.newKeyEvent({}, keycodes.map.escape, false):post()
end
-- application watcher stuff
local appwatcher
local initialState
local watcherCallback = function(title, state, app)
-- print(title, state, app)
if state == watcher.activated then
if fnutils.contains(module.showDockIn, title) then
if dockState then -- only change if it's actually different
module.showDock()
dockState = false
end
else
if not dockState then
module.hideDock()
dockState = true
end
end
end
end
module.start = function()
if not appwatcher then
initialState = module.isHidingOn()
dockState = initialState
appwatcher = watcher.new(watcherCallback):start()
-- match for current app
local frontAppTitle = application.frontmostApplication():title()
-- because the closing of the menu doesn't actually occur instantaneously in
-- isHidingOn, we can't just call the callback with fake parameters... it causes
-- AXShownMenuUIElement to be invalidated when the menu is "opened" again.
-- This passes the "hidden" parameter to hide/show to skip that step
if fnutils.contains(module.showDockIn, frontAppTitle) then
if dockState then
module.showDock(true)
dockState = false
end
else
if not dockState then
module.hideDock(true)
dockState = true
end
end
end
return module
end
module.stop = function()
if appwatcher then
appwatcher:stop()
appwatcher = nil
if initialState then
module.hideDock()
else
module.showDock()
end
initialState = nil
end
return module
end
-- set garbage collection on the module so we reset the dock to it's initial state when
-- restarting
return setmetatable(module, { __gc = module.stop }) Now, in the console, or in your
You'll see what I mean by the menu flashing whenever a change occurs... I've minimized this by tracking when we change it, but this means if you manually change it (by clicking on the separator yourself to select the menu item, or with the traditional key sequence -- I think Opt-Cmd-D), the script get out of sequence. It should survive a killing/restarting of the Dock, but haven't stress tested it, so I make no promises that this is 100% or that there might not be some edge cases that are missed, so YMMV. At any rate, it should be a place to start, if I've even sorta understood what you were asking for. If you or anyone finds a cleaner/easier way to do this, I'm all ears, but this is what I've come up with thus far. |
Beta Was this translation helpful? Give feedback.
-
I've tried for a couple hours with GPT to get it working but no luck.
Beta Was this translation helpful? Give feedback.
All reactions