Created
January 12, 2025 17:08
-
-
Save itissid/d9fb9500a27ab52a8d978e867094e8b9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-------------------------------------------------------------------------------- | |
-- HYPER KEYS + GLOBAL VARIABLES | |
-------------------------------------------------------------------------------- | |
local hyper = { "ctrl", "alt", "cmd" } | |
-- We'll store the 'targetScreen' the user picks. | |
local targetScreen = nil | |
-- Which application do we want to open/move? | |
-- You can change this to any bundle ID or app name you like (e.g., "Safari"). | |
local myAppBundleID = "com.openai.chat" | |
-------------------------------------------------------------------------------- | |
-- Global table to store frames, and a reference to the last screen used | |
-------------------------------------------------------------------------------- | |
local windowFramesByScreen = {} | |
local lastScreenName = nil | |
-------------------------------------------------------------------------------- | |
-- Store the current window frame for the window’s screen | |
-------------------------------------------------------------------------------- | |
local function storeWindowFrameForScreen(win) | |
local screen = win:screen() | |
if not screen then return end | |
local screenName = screen:name() | |
local frame = win:frame() | |
windowFramesByScreen[screenName] = frame | |
hs.alert.show("Stored frame for screen: " .. screenName) | |
end | |
-------------------------------------------------------------------------------- | |
-- Restore the frame if we have it stored for the given screen | |
-------------------------------------------------------------------------------- | |
local function restoreWindowFrameForScreen(win, screen) | |
local screenName = screen:name() | |
local storedFrame = windowFramesByScreen[screenName] | |
if storedFrame then | |
win:setFrame(storedFrame) | |
hs.alert.show("Restored frame on " .. screenName) | |
else | |
hs.alert.show("No stored frame for screen: " .. screenName) | |
end | |
end | |
-------------------------------------------------------------------------------- | |
-- STEP 1: DESIGNATE A SCREEN (ctrl + alt + cmd + [) | |
-------------------------------------------------------------------------------- | |
hs.hotkey.bind(hyper, "[", function() | |
-- Option 1: Mark the screen under the mouse pointer: | |
-- targetScreen = hs.mouse.getCurrentScreen() | |
-- Option 2: Mark the screen of the currently focused window: | |
local focusedWin = hs.window.focusedWindow() | |
if focusedWin then | |
targetScreen = focusedWin:screen() | |
else | |
targetScreen = hs.screen.mainScreen() -- fallback if no window is focused | |
end | |
if targetScreen then | |
hs.alert.show("Screen designated: " .. targetScreen:name()) | |
else | |
hs.alert.show("No screen found to designate.") | |
end | |
end) | |
-------------------------------------------------------------------------------- | |
-- STEP 2: OPEN OR MOVE APP TO THAT SCREEN & SPACE (ctrl + alt + cmd + ]) | |
-------------------------------------------------------------------------------- | |
hs.hotkey.bind(hyper, "]", function() | |
if not targetScreen then | |
hs.alert.show("No target screen selected (press [ first).") | |
return | |
end | |
-- Attempt to get the running application | |
local app = hs.application.get(myAppBundleID) | |
-- Debug info | |
for k, w in pairs(app:allWindows()) do | |
print(k, w:title(), w:role(), w:subrole()) | |
end | |
-- If the app isn't running, launch it | |
if not app then | |
hs.alert.show("App not running; opening " .. myAppBundleID) | |
hs.application.open(myAppBundleID) | |
-- Wait a moment for the app to launch | |
hs.timer.doAfter(1.0, function() | |
local newApp = hs.application.get(myAppBundleID) | |
if newApp then | |
moveAppToTargetScreen(newApp, targetScreen) | |
end | |
end) | |
else | |
-- If already running, just move it | |
moveAppToTargetScreen(app, targetScreen) | |
end | |
end) | |
-------------------------------------------------------------------------------- | |
-- HELPER FUNCTION: MOVE THE APP'S MAIN WINDOW TO THE CHOSEN SCREEN & SPACE | |
-------------------------------------------------------------------------------- | |
function moveAppToTargetScreen(app, screen) | |
local win = app:mainWindow() | |
if not win then | |
print(".") | |
print("W:No Main Window Found for " .. app:name().. "Trying another API.") | |
return | |
end | |
-- We will move the window to the user-selected screen | |
storeWindowFrameForScreen(win) | |
win:moveToScreen(screen) | |
win:focus() | |
-- If you want to keep the “exact same sizing” it had last time it was on this screen: | |
restoreWindowFrameForScreen(win, screen) | |
---------------------------------------------------------------------------- | |
-- (Optional) ALSO MOVE TO THE CURRENTLY ACTIVE SPACE ON THAT SCREEN: | |
---------------------------------------------------------------------------- | |
-- This part requires an "undocumented" Hammerspoon extension, or the | |
-- asmagill 'Spaces' spoon, to explicitly move windows between Spaces. | |
-- If you want the window to follow the *active* desktop on that screen, | |
-- you can do something like: | |
-- | |
-- local spaces = require("hs._asm.undocumented.spaces") | |
-- local uuid = screen:getUUID() | |
-- local activeSpace = spaces.activeSpaceOnScreen(uuid) | |
-- if activeSpace then | |
-- spaces.moveWindowToSpace(win:id(), activeSpace) | |
-- end | |
---------------------------------------------------------------------------- | |
hs.alert.show("Moved " .. app:name() .. " to " .. screen:name()) | |
lastScreenName = win:screen():name() | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment