Hammerspoon / hammerspoon

Staggeringly powerful macOS desktop automation with Lua
http://www.hammerspoon.org
MIT License
11.74k stars 578 forks source link

moving/resizing electron based apps (slack, vscode) #2316

Open Madd0g opened 4 years ago

Madd0g commented 4 years ago

All the electron applications I use (actually slack and vscode, but not spotify) have the same problem moving/resizing within a screen and between screens.

I have to press the shortcut key multiple times and every time it does part of the movement (resize only, then on next press it'll put the window in the right place). Also, only those apps are animating the resize, even though I have hs.window.animationDuration = 0.

All the shortcuts work in Finder/Spotify, but as soon as I switch to vscode, it starts animating and not fully working.

I thought it was just hs.grid based code, but seems like it happens with win:maximize() and win:moveToScreen().

Thanks

ogirginc commented 4 years ago

This might not be exclusive to Electron apps. I have a similar problem with Firefox Developer Edition when using it with or without an external display.

The example above is me trying to move FF from external to MBP's screen:

resizing-with-external-monitor

Madd0g commented 4 years ago

You're right, it happens with other apps as well. It's happening with iTerm for me.

The animation is one weird symptom and another is that my logic for moving the app between screens doesn't work properly, like it loses the screen, or the window/screen relationship.

ogirginc commented 4 years ago

Are you using a spoon or the API itself? I am using WindowGrid spoon which does depend on http://www.hammerspoon.org/docs/hs.grid.html

cmsj commented 4 years ago

Sometimes this kind of thing can be caused by apps having a different name in their Info.plist from the name of their .app bundle. If that’s the case here, maybe we can figure out where we’re using the wrong one, and try to fix it.

Madd0g commented 4 years ago

I'm using both hs.grid and simpler window methods like win:maximize() and both have the same problem

ogirginc commented 4 years ago

@cmsj do you need anything from me? Console log or any other info? I am not familiar with macOS internals & Lua but would love to help. :)

cmsj commented 4 years ago

Are there any errors in the Hammerspoon Console when the movements fail to trigger?

Madd0g commented 4 years ago

I get no errors.

In addition to simple win:maximize() having this problem, I have a big pile of custom code for moving windows between screens, so within a screen there's one problem (unwanted animation, need to call the function multiple times to get the desired result), but my own code is also failing to move the window to the next screen (maybe failing to detect which screen it's on, or maybe the total number of screens).

I'll dig more over the weekend, I'm sure I can figure out exactly where my code is failing, maybe that will provide some additional hint.

Madd0g commented 4 years ago

Also, yesterday, vscode was having this problem, today it doesn't have the problem, but now firefox does. I don't think it's related to specific apps.

maxandersen commented 4 years ago

im hittingthis at the moment.

with vscode focusedWindow() is nil but also happens when doing hs.application.frontmostApplication():focusedWindow() or :allWindows()

to hammerspoon its like vscode has no windows at all.

ogirginc commented 4 years ago

I am not sure, if this is only an Hammerspoon issue. I have tried using Mosaic and had similar problems.

However, Magnet seems to work just fine. 🤷

maxandersen commented 4 years ago

I also found that ecamm live's customziation docks are not detectable/filterable by hammerspoon. which is shame as their auto layout is bad and i would love to fix it.

ciamac commented 4 years ago

I'm having a similar issue with slack. Hammerspoon does not detect any slack windows:

> hs.application.find("Slack"):mainWindow()
nil

This seems to have happened recently.

codelahoma commented 3 years ago

I've been seeing this issue as well when using hs.grid.

My Emacs would sometimes require three calls, which in no particular order would resize height, resize width, and set origin.

Another datapoint that bolsters the theory that the issue is with untitled windows: After changing my Emacs window title to begin with Emacs |, it works on the first try every time.

sfdc-slumos commented 3 years ago

Hopefully not just pouring confusion on the fire, but I just had this happen to me with Slack, and found that restarting Slack fixed the issue. (For now...)

stpddream commented 3 years ago

i am having a similar issue with VScode that Hammerspoon does not detect any VSCode windows. After I changed my VSCode window title to start with VSCode -, it works without a problem. So it probably has something to do with untitled windows.

Madd0g commented 3 years ago

My Emacs would sometimes require three calls, which in no particular order would resize height, resize width, and set origin.

I'm actually amazed by this bug, some things work but require multiple "tries" and others simply don't work.

I have my own screen-moving logic and I also tried using the WinWin spoon, both using moveToScreen(), funnily mine never works with these windows (window:moveToScreen(previousScreen) just doesn't do anything) but WinWin stutters (making few wrong resizes in-place) but sometimes eventually does jump to the next screen.

just weird, man.

EDIT: one thing I noticed today is that the behavior is better when passing some more arguments to moveToScreen

-- works better!
window:moveToScreen(previousScreen, true, false)
mengelbrecht commented 3 years ago

For me the win:maximize() function requires 3 tries for apps like Firefox Nightly or VSCode to lead to a maximized window (note: I have set hs.window.animationDuration = 0).

  1. The first one sets the size of the window so the window reaches to the end of the screen. However, the position of the window is not changed. So it is not really maximized.
  2. The second try sets the topleft position of the window but does not change the size. This leads to the window being positioned correctly but being too small.
  3. The third try sets the size correctly to reach the end of the screen and the window is finally maximized.

When looking at the code of the maximize function one can see that it only calls _setFrame with the appropriate frame for the screen: https://github.com/Hammerspoon/hammerspoon/blob/53257ea54f376455c710e22af82834aba0621110/extensions/window/init.lua#L594 The _setFrame function performs 3 calls: setSize followed by setTopLeft and again setSize: https://github.com/Hammerspoon/hammerspoon/blob/53257ea54f376455c710e22af82834aba0621110/extensions/window/init.lua#L313

It seems that the _setFrame function aborts execution after setSize and/or setTopLeft?!

nimatrueway commented 2 years ago

This seems like a macOS or Chrome problem, because even macOS's built-in context menu for moving a window to other screen is broken.

image

tanpengsccd commented 2 years ago

So if we still want this functions, this app https://github.com/rxhanson/Rectangle can do this at this moment.

Sorry ,Rectangle does not work too.

tanpengsccd commented 2 years ago

So if we still want this functions, this app https://github.com/rxhanson/Rectangle can do this at this moment.

Sorry ,Rectangle does not work too.

oh I got U, they are both work when I restart vscode. It's weird !

chrisgrieser commented 2 years ago

I got the same issue, but solely with Finder. Even with the example taken from the "Getting Started" page.

Before migrating my window management to Hammerspoon, I used pure AppleScript for Window Management, and here, there are two methods for resizing windows, one of which caused the same issue while the other worked fine. Maybe this helps with fixing this issue?

# using bounds, everything works fine
# however, setting window bounds is only available for apps with proper AppleScript support
use framework "AppKit"
set allFrames to (current application's NSScreen's screens()'s valueForKey:"frame") as list
set max_x to item 1 of item 2 of item 1 of allFrames
set max_y to item 2 of item 2 of item 1 of allFrames

set x to 0.2 * max_x
set y to 0.1 * max_y
set w to 0.6 * max_x
set h to 0.8 * max_y
tell application "Finder" to set bounds of window 1 to {x, y, x + w, y + h}
# using size and position works for apps without regular AppleScript support
# but causes issues with some apps that do have regular AppleScript support
use framework "AppKit"
set allFrames to (current application's NSScreen's screens()'s valueForKey:"frame") as list
set max_x to item 1 of item 2 of item 1 of allFrames
set max_y to item 2 of item 2 of item 1 of allFrames

set x to 0.2 * max_x
set y to 0.1 * max_y
set w to 0.6 * max_x
set h to 0.8 * max_y

tell application "System Events"
    tell process "Finder"
        tell window 1
            set position to {x, y}
            set size to {w, h}
        end tell
       end tell
end tell
chrisgrieser commented 2 years ago

Okay, I looked into it and I am quite positive that the issue is caused by the difference between the position-size-method and the bounds-method I posted above.

After a bunch of testing, the issue 100% only occurs for apps with explicit AppleScript support. You can check whether an app has explicit AppleScript support by launching the Script Editor, and then selecting "open dictionary" in the file menu. You can also check it programmatically by looking whether a .sdef file exists somewhere in the app bundle (the .app).

1) If the app has explicit AppleScript support, the bounds-method and the position-size-method both work to resize a window. However, the position-size-method will lead to the glitching bug, where you sometimes need multiple attempts. The bounds-method works without any issue. 2) If the app does not have explicit AppleScript support, it can only be indirectly scripted via AppleScript by targeting process windows via tell application "System Events" .... Resizing windows only works via the position-size-method for these apps, but it works issue-free.

Consequently, you should use the bounds-method for AppleScript-capable apps, and the position-size-method for the others. From what I can tell, Hammerspoon seems to use purely the position-size-approach for all apps, resulting in the bug. Using the bounds-approach for the apps in question should also fix the problem for Hammerspoon. Unfortunately, I have zero knowledge of C and Objective-C, but maybe someone who does can fix this?

edit: there seems to be one exception for me: Sublime Text does not have explicit AppleScript support (i.e. the bounds-method does not work), but still has the issue when using the the position-size approach 😵‍💫


In the meantime, you can effectively use hs.applescript to create a workaround that fixes the window movement issue. Add this function to your init.lua and use resizingWorkaround instead of win:moveToUnit for moving windows. I have tested this and the issue with the windows not moving properly 100% stops for me with this workaround. (Sorry if the code isn't really clean, I only learned lua 2 days ago when I started using Hammerspoon.)

function resizingWorkaround(win, pos)
  local winApp = win:application():name()
  -- add Applescript-capable apps you are using to the if-condition below
  if (winApp == "Finder" or winApp == "Brave Browser" or winApp == "BusyCal" or winApp == "Safari") then
    hs.applescript([[
        use framework "AppKit"
        set allFrames to (current application's NSScreen's screens()'s valueForKey:"frame") as list
        set max_x to item 1 of item 2 of item 1 of allFrames
        set max_y to item 2 of item 2 of item 1 of allFrames
        ]] ..

        "set x to " .. pos.x .. " * max_x\n" ..
        "set y to " .. pos.y .. " * max_y\n" ..
        "set w to " .. pos.w .. " * max_x\n" ..
        "set h to " .. pos.h .. " * max_y\n" ..
        'tell application "' .. winApp .. '" to set bounds of front window to {x, y, x + w, y + h}'
    )
  else
    win:moveToUnit(pos)
  end
end
francoiscote commented 1 year ago

I got the same behavior with Chrome for a while (multisteps necessary to move and place windows, and animation is happening even if my animation duration setting is set to 0). My other electron based apps (slack, vscode) were always working fine.

The culprit on my end was the Gramarly Desktop app, which adds a little overlay on top of some apps. Just dropping this here in case this helps someone with a similar issue.

sfdc-slumos commented 1 year ago

Well after 2 years of no problems I'm back with Slack giving nil for every variation of focusedWindow I've tried. Restarting Slack doesn't work either. Is there no way to get a hs.window for Slack now?

Rhys-T commented 1 year ago

Looks like this is probably an issue with the AXEnhancedUserInterface attribute. Full explanation and workaround posted here: https://github.com/Hammerspoon/hammerspoon/issues/3224#issuecomment-1294359070

Madd0g commented 1 year ago

I was so annoyed that positioning windows is not working, that for some features, I developed a timer based loop hack that keeps issuing the command until the window seems to be in the right spot.

I'm almost sad to see it go, I kinda like to see the window twitching 4-5 times until it settles down in the desired position :)

https://user-images.githubusercontent.com/1171003/198724824-c8076d10-d62c-4074-810f-762a9d5bc57d.mp4

cmpadden commented 4 months ago

I am still experiencing this issue for specific applications, notably Firefox (Librewolf). Closed my issue to instead track this one, but more details can be found there:

https://github.com/Hammerspoon/hammerspoon/issues/3624

Quite hacky, but this is the workaround for moving windows with retries that I came up with; feedback is always welcome!

-- Convert relative `unitrect` to `rect` (couldn't get hs.window:fromUnitRect to work)
local function _unit_rect_to_rect(unit_rect)
    local screen_frame = hs.screen.mainScreen():frame()
    return hs.geometry.rect(
        screen_frame.x + (unit_rect.x * screen_frame.w),
        screen_frame.y + (unit_rect.y * screen_frame.h),
        unit_rect.w * screen_frame.w,
        unit_rect.h * screen_frame.h
    )
end

-- Temporary workaround: move windows until we confirm that they are at the frame that
-- we expect. Have a retry of 3 to prevent any unwanted infinite loops.
local function _move_to_unit_with_retries(geometry, window)
    window:moveToUnit(geometry)
    local retries = 3
    hs.timer.doUntil(function()
        return retries == 0 or window:frame():equals(_unit_rect_to_rect(geometry):floor())
    end, function()
        window:moveToUnit(geometry)
        retries = retries - 1
    end, 0.25)
end