Files
dotfiles/hammerspoon/window_management.lua

363 lines
9.0 KiB
Lua

-- luacheck: read_globals hs
local eventtap = require('hs.eventtap')
local grid = require('hs.grid')
local hotkey = require('hs.hotkey')
local mouse = require('hs.mouse')
local timer = require('hs.timer')
local window = require('hs.window')
-- configuration
local wm = {
animationDuration = 0.0,
gridSizes = { default = '30x20', interactive = '8x4' },
gridTextSize = 50,
margins = { w = 4, h = 4 }
}
-- initialize and register keybindings
function wm:init ()
-- setup
local bind = require('hs.hotkey').bind
local bindAndRepeat = self.bindAndRepeat
window.animationDuration = self.animationDuration
grid.setGrid(self.gridSizes.default)
grid.setMargins(self.margins)
grid.ui.textSize = self.gridTextSize
--
-- move and resize to preset grid locations
--
-- show interactive grid menu
bind({'cmd', 'ctrl'}, '1',
function()
grid.setGrid(self.gridSizes.interactive)
grid.show(
function()
grid.setGrid(self.gridSizes.default)
end
)
end
)
-- left half
bind({'cmd', 'ctrl'}, 'J', self.adjustWindow(0, 0, 15, 20))
-- right half
bind({'cmd', 'ctrl'}, 'L', self.adjustWindow(15, 0, 15, 20))
-- top half
bind({'cmd', 'ctrl'}, 'I', self.adjustWindow(0, 0, 30, 10))
-- bottom half
bind({'cmd', 'ctrl'}, 'K', self.adjustWindow(0, 10, 30, 10))
-- left narrow
bind({'ctrl', 'alt'}, 'U', self.adjustWindow(0, 0, 12, 20))
-- right narrow
bind({'ctrl', 'alt'}, 'O', self.adjustWindow(18, 0, 12, 20))
-- left wide
bind({'cmd', 'ctrl'}, 'U', self.adjustWindow(0, 0, 18, 20))
-- right wide
bind({'cmd', 'ctrl'}, 'O', self.adjustWindow(12, 0, 18, 20))
-- center super narrow
bind({'cmd', 'ctrl', 'alt'}, '\\', self.adjustWindow(10, 0, 10, 20))
-- center narrow small
bind({'ctrl', 'alt'}, '\\', self.adjustWindow(9, 0, 12, 20))
-- center narrow
bind({'cmd', 'ctrl'}, '\\', self.adjustWindow(7, 0, 16, 20))
-- center medium small
bind({'ctrl', 'alt'}, '\'', self.adjustWindow(6, 0, 18, 20))
-- center medium
bind({'cmd', 'ctrl'}, '\'', self.adjustWindow(5, 0, 20, 20))
-- center wide small
bind({'ctrl', 'alt'}, ';', self.adjustWindow(4, 0, 22, 20))
-- center wide
bind({'cmd', 'ctrl'}, ';', self.adjustWindow(3, 0, 24, 20))
-- maximized
bind({'cmd', 'ctrl'}, 'H', grid.maximizeWindow)
--
-- move and resize windows
--
bind({'cmd', 'ctrl', 'alt'}, 'F', self.resizeWindow(770, 634))
bind({'cmd', 'ctrl', 'alt'}, 'X', self.adjustWindow(0, 3, 10, 14))
-- resize windows
bindAndRepeat({'cmd', 'ctrl', 'alt'}, 'J', self.resizeWindowOnGrid(-1, 0))
bindAndRepeat({'cmd', 'ctrl', 'alt'}, 'L', self.resizeWindowOnGrid(1, 0))
bindAndRepeat({'cmd', 'ctrl', 'alt'}, 'I', self.resizeWindowOnGrid(0, -1))
bindAndRepeat({'cmd', 'ctrl', 'alt'}, 'K', self.resizeWindowOnGrid(0, 1))
-- move window relative
bindAndRepeat({'ctrl', 'alt'}, 'J', self.moveWindowOnGrid(-1, 0))
bindAndRepeat({'ctrl', 'alt'}, 'L', self.moveWindowOnGrid(1, 0))
bindAndRepeat({'ctrl', 'alt'}, 'I', self.moveWindowOnGrid(0, -1))
bindAndRepeat({'ctrl', 'alt'}, 'K', self.moveWindowOnGrid(0, 1))
-- enlarge horizontally
bindAndRepeat({'cmd', 'ctrl', 'shift'}, '\\',
self.resizeWindowOnGridSymmetrically(1, 0))
-- shrink horizontally
bindAndRepeat({'cmd', 'ctrl', 'shift'}, '\'',
self.resizeWindowOnGridSymmetrically(-1, 0))
--
-- move windows between spaces
--
bind({'ctrl', 'alt'}, 'left', self.moveWindowToSpace('left'))
bind({'ctrl', 'alt'}, 'right', self.moveWindowToSpace('right'))
bind({'ctrl', 'alt'}, '1', self.moveWindowToSpace('1'))
bind({'ctrl', 'alt'}, '2', self.moveWindowToSpace('2'))
bind({'ctrl', 'alt'}, '3', self.moveWindowToSpace('3'))
bind({'ctrl', 'alt'}, '4', self.moveWindowToSpace('4'))
bind({'ctrl', 'alt'}, '5', self.moveWindowToSpace('5'))
bind({'ctrl', 'alt'}, '6', self.moveWindowToSpace('6'))
bind({'ctrl', 'alt'}, '7', self.moveWindowToSpace('7'))
bind({'ctrl', 'alt'}, '8', self.moveWindowToSpace('8'))
bind({'ctrl', 'alt'}, '9', self.moveWindowToSpace('9'))
bind({'ctrl', 'alt'}, '0', self.moveWindowToSpace('0'))
--
-- move windows between displays
--
-- move to screen to the left
bind({'cmd', 'ctrl'}, ',',
function ()
local win = window.focusedWindow()
win:moveOneScreenWest()
grid.snap(win)
end
)
-- move to screen to the right
bind({'cmd', 'ctrl'}, '.',
function ()
local win = window.focusedWindow()
win:moveOneScreenEast()
grid.snap(win)
end
)
-- move to screen above
bind({'cmd', 'ctrl', 'alt'}, '.',
function ()
local win = window.focusedWindow()
win:moveOneScreenNorth()
grid.snap(win)
end
)
-- move to screen bellow
bind({'cmd', 'ctrl', 'alt'}, ',',
function ()
local win = window.focusedWindow()
win:moveOneScreenSouth()
grid.snap(win)
end
)
end
--
-- private methods
--
wm.bindAndRepeat = function (mod, key, fn)
hotkey.bind(mod, key, fn, nil, fn)
end
wm.adjustWindow = function (x, y, w, h)
return function ()
grid.adjustWindow(
function (cell)
cell.x = x
cell.y = y
cell.w = w
cell.h = h
end
)
end
end
wm.resizeWindow = function (w, h)
return function ()
local win = window.focusedWindow()
local f = win:frame()
f.w = w
f.h = h
win:setFrame(f)
end
end
wm.moveWindow = function(x, y)
return function ()
local win = window.focusedWindow()
local f = win:frame()
f.x = x
f.y = y
win:setFrame(f)
end
end
wm.moveWindowRelative = function (x, y)
return function ()
local win = window.focusedWindow()
local f = win:frame()
f.x = f.x + x
f.y = f.y + y
win:setFrame(f)
end
end
wm.moveWindowOnGrid = function (x, y)
return function ()
grid.adjustWindow(
function (cell)
local max = grid.getGrid()
if ((cell.x + x) + cell.w) <= max.w then
cell.x = cell.x + x
end
if ((cell.y + y) + cell.h) <= max.h then
cell.y = cell.y + y
end
end
)
end
end
wm.resizeWindowOnGrid = function (w, h)
return function ()
grid.adjustWindow(
function (cell)
local max = grid.getGrid()
if cell.x == 0 and cell.w == max.w then
if w < 0 then
cell.w = cell.w + w
else
cell.w = cell.w - w
cell.x = cell.x + w
end
elseif (cell.x + cell.w) >= max.w then
cell.w = cell.w - w
cell.x = cell.x + w
elseif cell.x == 0 then
cell.w = cell.w + w
else
cell.w = cell.w + (w * 2)
cell.x = cell.x - w
end
if cell.y == 0 and cell.h == max.h then
if h < 0 then
cell.h = cell.h + h
else
cell.h = cell.h - h
cell.y = cell.y + h
end
elseif (cell.y + cell.h) >= max.h then
cell.h = cell.h - h
cell.y = cell.y + h
elseif cell.y == 0 then
cell.h = cell.h + h
else
cell.h = cell.h - (h * 2)
cell.y = cell.y + h
end
end
)
end
end
wm.resizeWindowOnGridSymmetrically = function (w, h)
return function ()
grid.adjustWindow(
function (cell)
local max = grid.getGrid()
if w ~= 0 and cell.w + (w * 2) >= 2 then
if (cell.w + (w * 2)) <= max.w then
cell.w = cell.w + (w * 2)
cell.x = cell.x - w
elseif cell.w + w == max.w then
cell.w = max.w
cell.x = 0
end
end
if h ~= 0 and cell.h + (h * 2) >= 2 then
if (cell.h + (h * 2)) <= max.h then
cell.h = cell.h + (h * 2)
cell.y = cell.y - h
elseif cell.h + h == max.h then
cell.h = max.h
cell.y = 0
end
end
end
)
end
end
-- moveWindowToSpace
---
-- Requires ctrl+<left>/<right> and ctrl+<num> system keybindings, originally
-- from:
-- https://github.com/Hammerspoon/hammerspoon/issues/235#issuecomment-101069303
wm.moveWindowToSpace = function (direction)
return function()
local mouseOrigin = mouse.absolutePosition()
local win = window.focusedWindow()
local clickPoint = win:zoomButtonRect()
-- click and hold next to the zoom button close to the top of the window
clickPoint.x = clickPoint.x + clickPoint.w + 5
clickPoint.y = win:frame().y + 7
local mouseClickEvent = eventtap.event.newMouseEvent(
eventtap.event.types.leftMouseDown, clickPoint
)
mouseClickEvent:post()
timer.usleep(150000)
local nextSpaceDownEvent = eventtap.event.newKeyEvent(
{"ctrl"}, direction, true
)
nextSpaceDownEvent:post()
timer.usleep(150000)
local nextSpaceUpEvent = eventtap.event.newKeyEvent(
{"ctrl"}, direction, false
)
nextSpaceUpEvent:post()
timer.usleep(150000)
local mouseReleaseEvent = eventtap.event.newMouseEvent(
eventtap.event.types.leftMouseUp, clickPoint
)
mouseReleaseEvent:post()
timer.usleep(150000)
mouse.absolutePosition(mouseOrigin)
end
end
-- the end
return wm