Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hs.window:moveToScreen() behaves weirdly in macOS 12.4 when applied to a Google Chrome window. #3224

Open
liancheng opened this issue May 31, 2022 · 44 comments

Comments

@liancheng
Copy link

After upgrading to macOS 12.4 Monterey, I've noticed two issues:

  1. While moving a Google Chrome window using the moveAndResize function in the WinWin Spoon, the target window shows an animation while moving/resizing. Windows of other applications do not behave like this: they just immediately resize/move to the required shape and location.
  2. While moving a Google Chrome window to another screen using moveToScreen() in WinWin, instead of moving to the target screen, the target window shrinks to roughly half of the original length and width. Same as above, windows of other applications show the desired behavior.

Further debugging showed that calling hs.window:moveToScreen() directly shares the same issue.

You may reproduce this issue by opening a Google Chrome window, navigate to https://www.hammerspoon.org/docs/hs.window.html, and run the following snippet in the Hammerspoon console:

c = hs.window.find("Hammerspoon docs: hs.window")
s = c:screen()
c:moveToScreen(s:next())

Version information:

  • macOS: 12.4
  • Hammerspoon: 0.9.97 (6267)
  • Google Chrome: 101.0.4951.64 (Official Build) (x86_64)
@dasmurphy
Copy link

dasmurphy commented May 31, 2022

I did not use that function for my code, since i wrote some on my own...

Here is an excerpt from my code. I found some things from that online and derived this from it.

local function initWindowValues()
	local win = hs.window.focusedWindow()
	local f = win:frame()
	local screen = win:screen()
	local max = screen:frame()
	return win, f, screen, max
end

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "/", function ()
	local win, f, screen = initWindowValues()
	local screens = hs.screen.allScreens()
	local i = 0
	local screenNo = 0
	for i = 1, #screens do
		if screens[i]:id()==screen:id() then
			screenNo = i
		end
	end
	local newScreenNo = screenNo+1
	if newScreenNo>#screens then
		newScreenNo = 1
	end
	win:moveToScreen(screens[newScreenNo])
end)

May be this helps you a little bit to fix that function stuff.

@zackfern
Copy link

zackfern commented Jun 4, 2022

While moving a Google Chrome window using the moveAndResize function in the WinWin Spoon, the target window shows an animation while moving/resizing. Windows of other applications do not behave like this: they just immediately resize/move to the required shape and location.

I've recently observed the same thing happening with Firefox (101.0) on macOS 12.4 and Hammerspoon 0.9.97 (6267), but I'm using hs.window:setFrame. I recently upgraded to 1Password 8 around the same time I noticed this happening. I've realized that when I restart Firefox and don't use 1password autofill in the new session, my resizing behaves as expected (instantly, correctly, no animation). But once I use autofill the window movements animate and sometimes require multiple attempts to reach their correct size and location.

So out of curiosity, are you also using 1password 8 with the new Universal Autofill feature? If so, we might be seeing the same issue related to it. If not, sorry for the tangent on your thread. 😄

@BrianHicks
Copy link

That's exactly my situation too. 1PW 8, Chrome, latest macOS. I think you're on to something here.

@MuhammedZakir
Copy link
Contributor

Maybe related: pqrs-org/Karabiner-Elements#3075 (comment). Weirdly, the comment mentions the problem being solved after upgrading to macOS 12.4.

@asmagill
Copy link
Member

asmagill commented Jun 4, 2022

FWIW, I see the same behavior with Safari and 1password with Universal Autofill... and I'm already running 12.4, so...

Not sure what's up, but I'll check out the 1password forums later today and submit a bug report if it's not already known. I'm pretty sure it's not anything Hammerspoon is doing, so it's either a macOS change/bug that we're not aware of or an issue with 1password and how it's tapping into things for the autofill support.

@mkosma
Copy link

mkosma commented Jun 7, 2022

I'm having this issue, and I'm also a 1Password user.

  • Today's 1Password update didn't make a difference.
  • Closing the 1Password application then closing & relaunching Chrome seems to fix the behavior
  • Disabling the 1Password Chrome extension then closing & relaunching Chrome also seems to fix the behavior

I tried a couple other combos inconclusively - it could be that, in some percentage of cases, closing and relaunching Chrome temporarily fixes the problem regardless of whether 1Password is involved.

@NDuggan
Copy link

NDuggan commented Jun 14, 2022

Just adding another +1 for the weirdness @zackfern mentioned above. Only my browser window shows the animation and requires multiple attempts to move the window to the correct location.

App Versions

  • Hammerspoon 0.9.97
  • MacOS 12.4
  • Firefox 101.1
  • 1Password (8) for Mac 8.7.1

@danieldisu
Copy link

Another +1 here, happens in both chrome and edge and with 1password 7, not 8

App Versions

  • Hammerspoon 0.9.97
  • MacOS 12.3.1
  • Chrome Version 102.0.5005.115
  • Edge Version 102.0.1245.44
  • 1Password (7) for Mac 7.9.5

@kassi
Copy link

kassi commented Jun 24, 2022

Same issue here. I do have Karabiner Elements installed as well as 1password 8.
However the remappings I have defined in KE (mainly for switching tabs in Firefox) work as expected.

It's only that the shortcuts I created in hammerspoon to move windows fast to a desired position in a desired size don't work properly for Firefox. It's slow and often misplaced. Only a second hit moves it correctly (most of the times). I'm using setFrame.

  • Hammerspoon 0.9.97
  • MacOS 12.4
  • Firefox 91.10.0esr (for whatever reason not getting a newer version from company software center)
  • 1Password 8.7.1
  • Karabiner Elements 14.4.0

@mkosma
Copy link

mkosma commented Jul 31, 2022

For me, this issue has been resolved. It was in fact an odd interaction between 1Password's MacOS app and Chrome plugin.

I worked on this with Paul from 1Password support, sending him here and explaining how to set up Hammerspoon and reproduce the issue. I installed a desktop app update on July 12th and haven't seen this problem since.

FWIW my 1Password for Mac reports version 8.9.0, 80900001 on BETA channel.

@liancheng
Copy link
Author

Hmm, I'm not a 1Password user, but I'm using LastPass. I don't remember hitting this issue in Firefox, where LastPass is also installed. Also, after moving to an M1, I'm no longer able to reproduce this issue.

@holeyness
Copy link

I think this issue is now resolved.

@francoiscote
Copy link

I still have the issue.

  • macOS 12.5
  • Chrome 104.0.5112.79
  • Hammerspoon 0.9.97
  • 1Password Extension 2.3.7

@mkosma
Copy link

mkosma commented Aug 16, 2022 via email

@francoiscote
Copy link

I tested some more, and now I am pretty sure this is not related to 1Password. I was able to reproduce on a machine without 1Password installed, with a fresh installation of google chrome stable.

  • macOS 12.5 on a M1
  • Chrome 104.0.5112.101 (arm64)
  • Hammerspoon 0.9.97

@francoiscote
Copy link

and I found my own personal culprit. It is caused by the Gramarly Desktop app. As soon as I quit this app and restart Chrome, everything is instant again. Just dropping this here in case it helps someone else. It seemed that this can be caused by any app that creates overlay windows on top of electron-based apps.

@MoonSoul88
Copy link

try this
i solved this way

Accessibility → Zoom

if check this option "animation delay" Occurs immediately.
option check out and Reboot

스크린샷 2022-08-21 오전 9 22 16

nd Reboot!

@KingMario
Copy link

KingMario commented Sep 6, 2022

try this i solved this way

Accessibility → Zoom

if check this option "animation delay" Occurs immediately. option check out and Reboot

스크린샷 2022-08-21 오전 9 22 16

nd Reboot!

Great find!

It seems that both the first two checkboxes should be unchecked and a reboot is required, or restart the application (chrome or so) you want to move.

And this checkbox of advanced settings should also be unchecked:

image

@rcoedo
Copy link

rcoedo commented Sep 29, 2022

and I found my own personal culprit. It is caused by the Gramarly Desktop app. As soon as I quit this app and restart Chrome, everything is instant again. Just dropping this here in case it helps someone else. It seemed that this can be caused by any app that creates overlay windows on top of electron-based apps.

It was Grammarly for me as well. Thank you so much! This was driving me crazy 😂

@rcoedo
Copy link

rcoedo commented Oct 27, 2022

I have contacted Grammarly regarding this issue and haven't received any solution. Is there something that can be done from Hammerspoon's side?

I debugged the Lua code for setFrame, and everything seems correct. The problems seem to begin in libwindow.m, but unfortunately, I have no idea how to debug this part:

static int window__settopleft(lua_State* L) {

Can anybody shed some light on what's happening under the hood?

Thanks!

@Rhys-T
Copy link

Rhys-T commented Oct 28, 2022

I don't use 1Password or Grammarly myself, but they both sound like they would be using the Accessibility API to see what's going on in other apps. Chrome/Chromium and its derivatives (including Electron) don't create the full tree of accessibility info by default (for performance reasons, if I remember correctly) - they only do so if something sets the AXEnhancedUserInterface attribute to true on their AXApplication element. This is an undocumented property that is usually used to indicate that VoiceOver is running. 1Password and Grammarly are probably setting this themselves to force Chrome to generate the full tree.

Unfortunately, there seems to be a system bug that messes up attempts at moving/resizing a window through the Accessibility API when the window's app has AXEnhancedUserInterface set. This has caused problems for a number of window managers and similar tools. Phoenix is having issues with it right now, and it seems to be caused by 1Password in that case too. (It also caused problems for Hammerspoon in #904, but in that case the app that was setting AXEnhancedUserInterface was changed to not do so.)

Rectangle worked around it in this pull request, by temporarily disabling AXEnhancedUserInterface before moving/resizing, and setting it back afterwards. It might be worth adding similar logic to [HSwindow setTopLeft:] and [HSwindow setSize:]. For now, you can try doing something like this from your config:

-- win = some `hs.window` instance
local axApp = hs.axuielement.applicationElement(win:application())
local wasEnhanced = axApp.AXEnhancedUserInterface
if wasEnhanced then
	axApp.AXEnhancedUserInterface = false
end
win:setFrame(newFrame) -- or win:moveToScreen(someScreen), etc.
if wasEnhanced then
	axApp.AXEnhancedUserInterface = true
end

(Electron added an AXManualAccessibility property that forces the accessibility tree without triggering the bug, but it currently isn't working.)

@rcoedo
Copy link

rcoedo commented Oct 28, 2022

I don't use 1Password or Grammarly myself, but they both sound like they would be using the Accessibility API to see what's going on in other apps. Chrome/Chromium and its derivatives (including Electron) don't create the full tree of accessibility info by default (for performance reasons, if I remember correctly) - they only do so if something sets the AXEnhancedUserInterface attribute to true on their AXApplication element. This is an undocumented property that is usually used to indicate that VoiceOver is running. 1Password and Grammarly are probably setting this themselves to force Chrome to generate the full tree.

Unfortunately, there seems to be a system bug that messes up attempts at moving/resizing a window through the Accessibility API when the window's app has AXEnhancedUserInterface set. This has caused problems for a number of window managers and similar tools. Phoenix is having issues with it right now, and it seems to be caused by 1Password in that case too. (It also caused problems for Hammerspoon in #904, but in that case the app that was setting AXEnhancedUserInterface was changed to not do so.)

Rectangle worked around it in this pull request, by temporarily disabling AXEnhancedUserInterface before moving/resizing, and setting it back afterwards. It might be worth adding similar logic to [HSwindow setTopLeft:] and [HSwindow setSize:]. For now, you can try doing something like this from your config:

-- win = some `hs.window` instance
local axApp = hs.axuielement.applicationElement(win:application())
local wasEnhanced = axApp.AXEnhancedUserInterface
if wasEnhanced then
	axApp.AXEnhancedUserInterface = false
end
win:setFrame(newFrame) -- or win:moveToScreen(someScreen), etc.
if wasEnhanced then
	axApp.AXEnhancedUserInterface = true
end

(Electron added an AXManualAccessibility property that forces the accessibility tree without triggering the bug, but it currently isn't working.)

This is amazing. Thank you so much for sharing it!

I implemented a couple of helper functions that implement your suggested fix. It is working like a charm:

function axHotfix(win)
  if not win then win = hs.window.frontmostWindow() end

  local axApp = hs.axuielement.applicationElement(win:application())
  local wasEnhanced = axApp.AXEnhancedUserInterface
  if wasEnhanced then
    axApp.AXEnhancedUserInterface = false
  end

  return function()
    if wasEnhanced then
      axApp.AXEnhancedUserInterface = true
    end
  end
end

function withAxHotfix(fn, position)
  if not position then position = 1 end
  return function(...)
    local args = {...}
    local revert = axHotfix(args[position])
    fn(...)
    revert()
  end
end

The former wraps around the received window object, and returns a function that reverts the hack. The latter wraps a function.

The usage is something like this:

local patchedPushWindowUp = withAxHotfix(grid.pushWindowUp)

Or, if the wrapped function receives the window in a different position, something like this:

local patchedAdjustWindow = withAxHotfix(grid.adjustWindow, 2)

If no window is passed, it takes the frontmost window. I think this matches most of hammerspoon APIs.

Cheers!

@Rhys-T
Copy link

Rhys-T commented Oct 28, 2022

I think that 'revert' function inside axHotfix needs to be setting AXEnhancedUserInterface back to true instead of false, to make sure Chrome, etc. keep updating the a11y tree for Grammarly/1Password/whatever. Looks good otherwise.

@rcoedo
Copy link

rcoedo commented Oct 28, 2022

Whoops, I messed up when I copy-pasted some code. I'll edit it, so people don't get confused.

Thanks!

@asmagill
Copy link
Member

This would be a fairly easy fix to add to the hs.window module (which underpins grid, layout, etc.) if it's a reliable fix for this issue... is it just movement and resizing that needs to make this check, or are there other hs.window methods that could use a wrapper as well?

@Rhys-T
Copy link

Rhys-T commented Oct 29, 2022

The only thing I know of is movement and resizing, but I can't say for sure.

Doing some experimenting in the Hammerspoon console, it looks like what's happening is that when AXEnhancedUserInterface is on1, setting AXSize or AXPosition will start smoothly animating the window to the new size/position rather than changing it immediately - but it also interrupts any such animation that's already in progress. So if you set AXSize and AXPosition in rapid succession (like win:setFrame(...) does), whichever one is set first doesn't have a chance to finish animating, and ends up stopping at some intermediate state. That doesn't quite explain why both the size and position seem to be ending up wrong, but I suppose it might be asynchronous enough that the order in which they actually start could vary…

(On a possibly-related note, why does setFrame set the size, position, and then the size again?)

Footnotes

  1. And, presumably, the zoom settings mentioned above have been on since the last boot. Haven't gotten around to rebooting to test this, though, and relaunching the app doesn't seem to be resetting it for me.

@Sangdol
Copy link

Sangdol commented Nov 5, 2022

Thanks for the explanation @Rhys-T.

Although disabling AXEnhancedUserInterface works, I ended up taking a different approach since it made transitions laggy.

My approach is to move the window to the top left first and then make it a full screen.

The code looks like this:

function fullscreen(win)
  local screenFrame = win:screen():frame()

  win:setTopLeft(screenFrame.x, screenFrame.y)

  -- Waiting 0.4 seconds to make the two step transition work
  -- You might need to adjust this.
  hs.timer.usleep(0.4 * 1000 * 1000) 

  win:setFrame(screenFrame)

  return win
end

This makes it two steps with a delay, but I found it quicker than controlling AXEnhancedUserInterface.

This is quite hacky, but I hope someone finds it helpful.

@Rhys-T
Copy link

Rhys-T commented Nov 5, 2022

Good to know. If that workaround is slowing things down, maybe it shouldn't be built into hs.window, or at least should have some sort of flag to enable/disable it.

@Rhys-T
Copy link

Rhys-T commented Nov 15, 2022

Possibly related to the lag mentioned above: Chrome apparently has a recent bug (#1364487) causing it to lag whenever anything turns its AXEnhancedUserInterface attribute off. More discussion in rxhanson/Rectangle#912, since it's causing problems for their version of this workaround.

@Rhys-T
Copy link

Rhys-T commented Dec 20, 2022

Possibly related to the lag mentioned above: Chrome apparently has a recent bug (#1364487) causing it to lag whenever anything turns its AXEnhancedUserInterface attribute off.

Looks like that particular bug has been fixed in recent Chromium/Chrome versions. May take some time to propagate to other Chromium-based browsers, though (and Electron apps, if they're affected).

@pitkling
Copy link

I ran across this issue today when installing a new system (amongst others, Safari was affected). I was a bit surprised since I never had this problem on another, almost identical system. Or at least so I thought. In fact, both systems show the behavior, but only if my Hammerspoon config is (re-)loaded while the corresponding app is still open. If Hammerspoon has loaded and I quit and reopen the app, everything works fine. My other system always loaded Hammerspoon at startup and I rarely reloaded the config…

@Rhys-T
Copy link

Rhys-T commented Jul 27, 2023

@pitkling That's odd - as far as I know, the only thing that should be controlling whether the issue occurs or not is the state of the app that's being controlled (specifically, its AXEnhancedUserInterface property), not anything about the app that's doing the controlling (i.e. Hammerspoon).

Are you by any chance using the VimMode spoon, or any other HS code that might turn on AXEnhancedUserInterface itself? That could cause the behavior you're seeing, if it happens when HS first loads - any app that got that set would have the problem, but if you relaunched the app and the spoon didn't set that flag again, it would start working properly again.

@pitkling
Copy link

@Rhys-T: You're completely right, thanks for pointing that out! I actually started to use VimMode recently and deactivating it removes the problem altogether. I wasn't aware that VimMode is also affected by this, but makes sense (and similar issues seem to have been reported there, e.g., dbalatero/VimMode.spoon#45 and dbalatero/VimMode.spoon#68).

@Rhys-T
Copy link

Rhys-T commented Jul 27, 2023

@pitkling Sounds like with VimMode loaded, the role of "app that turns on everyone else's AXEnhancedUserInterface attribute so it can see inside Chrome, etc." is played by Hammerspoon itself. Still, the same workarounds from above (e.g. #3224 (comment)) should work in that case, if you want to keep using VimMode.

@pitkling
Copy link

@Rhys-T Works like a charm, thanks.

Just for reference, in case someone else wants to use that workaround with window object methods like moveToUnit or maximize: Remember that you have to patch the suitable metatable methods:

local windowMT = hs.getObjectMetatable("hs.window")
windowMT.maximize   = withAxHotfix(windowMT.maximize)
windowMT.moveToUnit = withAxHotfix(windowMT.moveToUnit)

Also, just wondering:

Good to know. If that workaround is slowing things down, maybe it shouldn't be built into hs.window, or at least should have some sort of flag to enable/disable it.

Is it still considered to built this into hs.window? As I understand, the mentioned lag came probably from a Chromium/Chrome bug that has been fixed by now? So far I did non observe any lag on my system with this method.

kbd added a commit to kbd/setup that referenced this issue Aug 4, 2023
Hammerspoon/hammerspoon#3224

Unfortunately this loses resize animations, but resizes work the first time.
@etern
Copy link

etern commented Jan 8, 2024

For me, AXEnhancedUserInterface solution not working.
It's this app: Input Source Pro

Quit this app and restart computer fixed my issue

@Rhys-T
Copy link

Rhys-T commented Jan 8, 2024

Hmm… that definitely sounds like the sort of app that might turn on AXEnhancedUserInterface and cause this issue. I'm not sure why the workarounds above wouldn't work in that case, though.

@xaviergmail
Copy link

xaviergmail commented Jun 7, 2024

I am still experiencing this, albeit just on Firefox. The frustrating part is that I'm using hs.grid.show() and it won't resize or move properly, at all! I have to repeat it more than once or twice for the window to be adjusted to the desired grid. I've also tried using the workarounds described above, mainly simply calling withAxHotfix(hs.grid.toggleShow)(), but no luck.

Macos 14.1.1
Hammerspoon 0.9.100 (6815)
1Password 8.10.20

@xaviergmail
Copy link

I don't know why I hadn't thought of this earlier, but hs.grid.toggleShow() is non-blocking. Resorting to patching the hs.window metatable works.. kind of! hs.grid users will want to patch setFrameScreenInBounds.

Another thing I noticed is that if we set it back to true before hs.window.animationDuration, the resizing/moving bug will still occur.

The following works for me for hs.grid!

Fix for animationDuration

local function axHotfix(win)
    if not win then win = hs.window.frontmostWindow() end

    local axApp = hs.axuielement.applicationElement(win:application())
    local wasEnhanced = axApp.AXEnhancedUserInterface
    axApp.AXEnhancedUserInterface = false

    return function()
        hs.timer.doAfter(hs.window.animationDuration * 2, function()
            axApp.AXEnhancedUserInterface = wasEnhanced
        end)
    end
end

local function withAxHotfix(fn, position)
    if not position then position = 1 end
    return function(...)
        local revert = axHotfix(select(position, ...))
        fn(...)
        revert()
    end
end

Fix for hs.grid

local windowMT = hs.getObjectMetatable("hs.window")
windowMT._setFrameInScreenBounds = windowMT._setFrameInScreenBounds or windowMT.setFrameInScreenBounds -- Keep the original, if needed
windowMT.setFrameInScreenBounds = withAxHotfix(windowMT.setFrameInScreenBounds)

(double-posting to notify anyone watching this that might not get the edited version through email notifications)

@Rhys-T
Copy link

Rhys-T commented Jun 7, 2024

I am still experiencing this, albeit just on Firefox.

I noticed the other day that Firefox was getting AXEnhancedUserInterface set even when I wasn't running anything that should be doing that. I just checked the Firefox source for any mention of AXEnhancedUserInterface, and it looks like Firefox actually sets it itself whenever another app asks for its AXRole, to make Voice Control work properly. A quick test from the HS console seems to confirm this:

local fx = hs.axuielement.applicationElement(hs.application'Firefox')
fx.AXEnhancedUserInterface = false
return fx.AXEnhancedUserInterface, fx.AXRole, fx.AXEnhancedUserInterface
-- false	AXApplication	true

I'm not sure why the workarounds aren't working for you. Maybe something hs.grid is calling ends up re-fetching Firefox's AXRole somehow? (It would have to be the AXApplication element's role, though - getting the role of e.g. an AXWindow doesn't trigger it.) Never mind, you already figured it out and posted a fraction of a second before I could post this 😄 Glad to hear you found a solution.

@xaviergmail
Copy link

Never mind, you already figured it out and posted a fraction of a second before I could post this 😄 Glad to hear you found a solution.

Thanks for looking into FF! I had tried disabling everything requiring accessibility permissions and it was still enabled, I thought it was my work's mdm/antivirus/spyware for a moment

@serban
Copy link

serban commented Jun 16, 2024

Relevant Firefox bugs tracking this issue:

@muescha
Copy link
Contributor

muescha commented Oct 15, 2024

Fix for hs.grid

local windowMT = hs.getObjectMetatable("hs.window")
windowMT._setFrameInScreenBounds = windowMT._setFrameInScreenBounds or windowMT.setFrameInScreenBounds -- Keep the original, if needed
windowMT.setFrameInScreenBounds = withAxHotfix(windowMT.setFrameInScreenBounds)

I patched just the setFrame because maximize and setFrameInScreenBounds also use it.

local windowMT = hs.getObjectMetatable("hs.window")
windowMT.setFrame = withAxHotfix(windowMT.setFrame)

but one nitpick I see here because of the delayed reset:

when I have this commands:

    local externalScreen = hs.screen.allScreens()[2]
    local window = hs.window.frontmostWindow()
    window:moveToScreen(externalScreen, false, true)
    window:setFrame({
            x = exterwnalScreen:frame().x + (externalScreen:frame().w / 2),
            y = externalScreen:frame().y,
            w = externalScreen:frame().w / 2,
            h = externalScreen:frame().h,
    })

then the hotfix is fired twice. one with the moveToScreen and second with the setFrame should there be a debounce of the multiple revert()?

@liancheng
Copy link
Author

In my environment, this issue disappeared for quite a while but resurfaced recently, but with some differences:

  • OS version: Sonoma 14.7

  • HS version: 1.0.0 (6864)

  • Symptoms:

    Similar to the original symptoms I wrote in the initial post, but this time happening to most windows. All the system application windows are fine, though, e.g., Music, Finder, Settings.

I applied the trick suggested by @xaviergmail in this comment, and it worked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests