From 930342d55b5aefc46107b3287854764e6a78b157 Mon Sep 17 00:00:00 2001 From: Michael Mogenson Date: Wed, 22 May 2024 12:32:19 -0400 Subject: [PATCH] More updates to space switching Use a coroutine to handle focusing a space after switching. MacOS will sometimes switch the focus of a window to another application when moving a window to a new space. Try to focus a window 3 times when it is added to a new space. Revert to dragging a window to a new space with a mouse. Using the hs.window:move() method fails when the old and new space are on the same screen. We need the mouse to "pick up" a window so that it moves with the space change. --- init.lua | 95 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/init.lua b/init.lua index bc9a144..12311eb 100644 --- a/init.lua +++ b/init.lua @@ -40,9 +40,13 @@ local Watcher = hs.uielement.watcher local Window = hs.window local WindowFilter = hs.window.filter local leftClick = hs.eventtap.leftClick +local leftMouseDown = hs.eventtap.event.types.leftMouseDown +local leftMouseDragged = hs.eventtap.event.types.leftMouseDragged +local leftMouseUp = hs.eventtap.event.types.leftMouseUp +local newMouseEvent = hs.eventtap.event.newMouseEvent +local operatingSystemVersion = hs.host.operatingSystemVersion local partial = hs.fnutils.partial local rectMidPoint = hs.geometry.rectMidPoint -local operatingSystemVersion = hs.host.operatingSystemVersion local PaperWM = {} PaperWM.__index = PaperWM @@ -271,25 +275,46 @@ local function focusSpace(space, window) return end - -- move cursor to center of screen - local point = rectMidPoint(screen:fullFrame()) - Mouse.absolutePosition(point) - -- focus provided window or first window on new space window = window or getFirstVisibleWindow(window_list[space], screen) - if window then - window:focus() - -- MacOS will sometimes switch to another window of the same applications on a different space - -- Setup a timer to check that the requested window stays focused - local function focusCheck() - if window ~= Window.focusedWindow() then - window:focus() + + local do_space_focus = coroutine.wrap(function() + if window then + local function check_focus(win, n) + local focused = true + for i = 1, n do -- ensure that window focus does not change + focused = focused and (Window.focusedWindow() == win) + if not focused then return false end + coroutine.yield(false) -- not done + end + return focused end + repeat + window:focus() + coroutine.yield(false) -- not done + until (Spaces.focusedSpace() == space) and check_focus(window, 3) + else + local point = screen:frame() + point.x = point.x + (point.w // 2) + point.y = point.y - 4 + repeat + leftClick(point) -- click on menubar + coroutine.yield(false) -- not done + until Spaces.focusedSpace() == space end - for i = 1, 3 do Timer.doAfter(i * Window.animationDuration, focusCheck) end - elseif Spaces.spaceType(space) == "user" then - leftClick(point) -- if there are no windows and the space is a user space then click - end + + -- move cursor to center of screen + Mouse.absolutePosition(rectMidPoint(screen:frame())) + return true -- done + end) + + local start_time = Timer.secondsSinceEpoch() + Timer.doUntil(do_space_focus, function(timer) + if Timer.secondsSinceEpoch() - start_time > 4 then + PaperWM.logger.ef("focusSpace() timeout! space %d focused space %d", space, Spaces.focusedSpace()) + timer:stop() + end + end, Window.animationDuration) end ---start automatic window tiling @@ -1081,41 +1106,43 @@ function PaperWM:moveWindowToSpace(index, window) -- https://github.com/Hammerspoon/hammerspoon/issues/3636 local version = operatingSystemVersion() if version.major * 100 + version.minor >= 1405 then - local do_window_move = coroutine.wrap(function() - repeat - coroutine.yield(false) -- not done - until Spaces.activeSpaceOnScreen(screen:id()) == new_space + local start_point = focused_window:frame() + start_point.x = start_point.x + start_point.w // 2 + start_point.y = start_point.y + 4 - local window_frame = focused_window:frame() - local screen_frame = getCanvas(screen) + local end_point = screen:frame() + end_point.x = end_point.x + end_point.w // 2 + end_point.y = end_point.y + self.window_gap + 4 - -- center window and fit to new screen - window_frame.w = math.min(window_frame.w, screen_frame.w) - window_frame.h = math.min(window_frame.h, screen_frame.h) - window_frame.x = screen_frame.x + (screen_frame.w // 2) - - (window_frame.w // 2) - window_frame.y = screen_frame.y + (screen_frame.h // 2) - - (window_frame.h // 2) + local do_window_drag = coroutine.wrap(function() + -- drag window half way there + start_point.x = start_point.x + ((end_point.x - start_point.x) // 2) + start_point.y = start_point.y + ((end_point.y - start_point.y) // 2) + newMouseEvent(leftMouseDragged, start_point):post() + coroutine.yield(false) -- not done - focused_window:setFrame(window_frame) + -- finish drag and release + newMouseEvent(leftMouseUp, end_point):post() + -- wait until window registers as on the new space repeat coroutine.yield(false) -- not done until Spaces.windowSpaces(focused_window)[1] == new_space -- add window and tile self:addWindow(focused_window) - focused_window:focus() self:tileSpace(old_space) self:tileSpace(new_space) + focusSpace(new_space, focused_window) return true -- done end) - -- switch spaces, wait for space to be ready, move window, wait for window to be ready + -- pick up window, switch spaces, wait for space to be ready, drag and drop window, wait for window to be ready + newMouseEvent(leftMouseDown, start_point):post() Spaces.gotoSpace(new_space) local start_time = Timer.secondsSinceEpoch() - Timer.doUntil(do_window_move, function(timer) - if Timer.secondsSinceEpoch() - start_time > 5 then + Timer.doUntil(do_window_drag, function(timer) + if Timer.secondsSinceEpoch() - start_time > 4 then self.logger.ef("moveWindowToSpace() timeout! new space %d curr space %d window space %d", new_space, Spaces.activeSpaceOnScreen(screen:id()), Spaces.windowSpaces(focused_window)[1]) timer:stop()