Skip to content

Instantly share code, notes, and snippets.

@raine
Created April 1, 2015 08:37
Show Gist options
  • Save raine/09a9fee03af2615283df to your computer and use it in GitHub Desktop.
Save raine/09a9fee03af2615283df to your computer and use it in GitHub Desktop.
local application = require "mjolnir.application"
local eventtap = require "mjolnir._asm.eventtap"
local fnutils = require "mjolnir.fnutils"
local hotkey = require "mjolnir.hotkey"
local keycodes = require "mjolnir.keycodes"
local timer = require "mjolnir._asm.timer"
local transform = require "mjolnir.sk.transform"
local window = require "mjolnir.window"
local alert = require "mjolnir.alert"
-- extensions
local ext = {
frame = {},
win = {},
app = {},
utils = {}
}
-- saved window positions
ext.win.positions = {}
-- window extension settings
ext.win.margin = 0
ext.win.animate = false
ext.win.fixenabled = false
ext.win.fullframe = false
-- check if simbl is running
-- if so, then it's for menubarhider,
-- and fullframe should be anabled
if os.execute("ps xc | grep -q SIMBL") then
ext.win.fullframe = true
end
-- returns frame pushed to screen edge
function ext.frame.push(screen, direction)
local m = ext.win.margin
local h = screen.h - m
local w = screen.w - m
local x = screen.x + m
local y = screen.y + m
local frames = {
up = function()
return { x = x, y = y, w = w - m, h = h / 2 - m }
end,
down = function()
return { x = x, y = y + h / 2 - m, w = w - m, h = h / 2 - m }
end,
left = function()
return { x = x, y = y, w = w / 2 - m, h = h - m }
end,
right = function()
return { x = x + w / 2 - m, y = y, w = w / 2 - m, h = h - m }
end
}
return frames[direction]()
end
-- returns frame moved by ext.win.margin
function ext.frame.nudge(frame, screen, direction)
local m = 75
local h = screen.h
local w = screen.w
local x = screen.x
local y = screen.y
local modifyframe = {
up = function(frame)
frame.y = math.max(y, frame.y - m)
return frame
end,
down = function(frame)
frame.y = math.min(y + h - frame.h, frame.y + m)
return frame
end,
left = function(frame)
frame.x = math.max(x, frame.x - m)
return frame
end,
right = function(frame)
frame.x = math.min(x + w - frame.w, frame.x + m)
return frame
end
}
return modifyframe[direction](frame)
end
-- returns frame sent to screen edge
function ext.frame.send(frame, screen, direction)
local m = ext.win.margin
local h = screen.h - m
local w = screen.w - m
local x = screen.x + m
local y = screen.y + m
local modifyframe = {
up = function(frame) frame.y = y end,
down = function(frame) frame.y = y + h - frame.h - m end,
left = function(frame) frame.x = x end,
right = function(frame) frame.x = x + w - frame.w - m end
}
modifyframe[direction](frame)
return frame
end
-- returns frame fited inside screen
function ext.frame.fit(frame, screen)
frame.w = math.min(frame.w, screen.w - ext.win.margin * 2)
frame.h = math.min(frame.h, screen.h - ext.win.margin * 2)
return frame
end
-- returns frame centered inside screen
function ext.frame.center(frame, screen)
frame.x = screen.w / 2 - frame.w / 2 + screen.x
frame.y = screen.h / 2 - frame.h / 2 + screen.y
return frame
end
-- get screen frame
function ext.win.screenframe(win)
local funcname = ext.win.fullframe and "fullframe" or "frame"
local winscreen = win:screen()
return winscreen[funcname](winscreen)
end
-- set frame
function ext.win.set(win, frame, time)
time = time or 0.15
if ext.win.animate then
transform:setframe(win, frame, time)
else
win:setframe(frame)
end
end
-- ugly fix for problem with window height when it's as big as screen
function ext.win.fix(win)
if ext.win.fixenabled then
local screen = ext.win.screenframe(win)
local frame = win:frame()
if (frame.h > (screen.h - ext.win.margin * 2)) then
frame.h = screen.h - ext.win.margin * 10
ext.win.set(win, frame)
end
end
end
-- pushes window in direction
function ext.win.push(win, direction)
local screen = ext.win.screenframe(win)
local frame
frame = ext.frame.push(screen, direction)
ext.win.fix(win)
ext.win.set(win, frame)
end
-- nudges window in direction
function ext.win.nudge(win, direction)
local screen = ext.win.screenframe(win)
local frame = win:frame()
frame = ext.frame.nudge(frame, screen, direction)
ext.win.set(win, frame, 0.05)
end
-- push and nudge window in direction
function ext.win.pushandnudge(win, direction)
ext.win.push(win, direction)
ext.win.nudge(win, direction)
end
-- sends window in direction
function ext.win.send(win, direction)
local screen = ext.win.screenframe(win)
local frame = win:frame()
frame = ext.frame.send(frame, screen, direction)
ext.win.fix(win)
ext.win.set(win, frame)
end
-- centers window
function ext.win.center(win)
local screen = ext.win.screenframe(win)
local frame = win:frame()
frame = ext.frame.center(frame, screen)
ext.win.set(win, frame)
end
-- fullscreen window with margin
function ext.win.full(win)
local screen = ext.win.screenframe(win)
local frame = {
x = ext.win.margin + screen.x,
y = ext.win.margin + screen.y,
w = screen.w - ext.win.margin * 2,
h = screen.h - ext.win.margin * 2
}
ext.win.fix(win)
ext.win.set(win, frame)
-- center after setting frame, fixes terminal
ext.win.center(win)
end
-- throw to next screen, center and fit
function ext.win.throw(win, direction)
local framefunc = ext.win.fullframe and "fullframe" or "frame"
local screenfunc = direction == "next" and "next" or "previous"
local winscreen = win:screen()
local throwscreen = winscreen[screenfunc](winscreen)
local screen = throwscreen[framefunc](throwscreen)
local frame = win:frame()
frame.x = screen.x
frame.y = screen.y
frame = ext.frame.fit(frame, screen)
frame = ext.frame.center(frame, screen)
ext.win.fix(win)
ext.win.set(win, frame)
win:focus()
-- center after setting frame, fixes terminal and macvim
ext.win.center(win)
end
-- set window size and center
function ext.win.size(win, size)
local screen = ext.win.screenframe(win)
local frame = win:frame()
frame.w = size.w
frame.h = size.h
frame = ext.frame.fit(frame, screen)
frame = ext.frame.center(frame, screen)
ext.win.set(win, frame)
end
-- save and restore window positions
function ext.win.pos(win, option)
local id = win:application():bundleid()
local frame = win:frame()
-- saves window position if not saved before
if option == "save" and not ext.win.positions[id] then
ext.win.positions[id] = frame
end
-- force update saved window position
if option == "update" then
ext.win.positions[id] = frame
end
-- restores window position
if option == "load" and ext.win.positions[id] then
ext.win.set(win, ext.win.positions[id])
end
end
-- cycle application windows
-- https://github.com/nifoc/dotfiles/blob/master/mjolnir/cycle.lua
function ext.win.cycle(win)
local windows = win:application():allwindows()
windows = fnutils.filter(windows, function(win) return win:isstandard() end)
if #windows >= 2 then
table.sort(windows, function(a, b) return a:id() < b:id() end)
local activewindowindex = fnutils.indexof(windows, win)
if activewindowindex then
activewindowindex = activewindowindex + 1
if activewindowindex > #windows then activewindowindex = 1 end
windows[activewindowindex]:focus()
end
end
end
-- launch or focus or cycle app
function ext.app.launchorfocus(app)
local focusedwindow = window.focusedwindow()
local currentapp = focusedwindow and focusedwindow:application():title() or nil
if currentapp == app then
if focusedwindow then
local appwindows = focusedwindow:application():allwindows()
local visiblewindows = fnutils.filter(appwindows, function(win) return win:isstandard() end)
if #visiblewindows == 0 then
-- try sending cmd-n for new window if no windows are visible
ext.utils.newkeyevent({ cmd = true }, "n", true):post()
ext.utils.newkeyevent({ cmd = true }, "n", false):post()
else
-- cycle windows if there are any
ext.win.cycle(focusedwindow)
end
end
else
application.launchorfocus(app)
end
end
-- smart browser launch or focus or cycle
function ext.app.browser()
local browsers = { "Safari", "Google Chrome" }
local runningapps = application.runningapplications()
local focusedwindow = window.focusedwindow()
local currentapp = focusedwindow and focusedwindow:application():title() or nil
-- filter running applications by browsers array
local runningbrowsers = fnutils.map(browsers, function(browser)
return fnutils.find(runningapps, function(app) return app:title() == browser end)
end)
-- try to get index of current app in running browsers
-- this means - is one of the browsers currently selected
local currentindex = fnutils.indexof(fnutils.map(runningbrowsers, function(app)
return app:title()
end), currentapp)
-- if there are no browsers launch the first (default) one
-- otherwise cycle between browser windows or between browsers depending on situation
if #runningbrowsers == 0 then
ext.app.launchorfocus(browsers[1])
else
local browserindex = currentindex and (currentindex % #runningbrowsers) + 1 or 1
ext.app.launchorfocus(runningbrowsers[browserindex]:title())
end
end
-- properly working newkeyevent
-- https://github.com/nathyong/mjolnir.ny.tiling/blob/master/spaces.lua
function ext.utils.newkeyevent(modifiers, key, pressed)
local keyevent
keyevent = eventtap.event.newkeyevent({}, "", pressed)
keyevent:setkeycode(keycodes.map[key])
keyevent:setflags(modifiers)
return keyevent
end
-- apply function to a window with optional params, saving it's position for restore
function dowin(fn, param)
local win = window.focusedwindow()
if win and not win:isfullscreen() then
ext.win.pos(win, "save")
fn(win, param)
end
end
-- for simple hotkey binding
function bindwin(fn, param)
return function() dowin(fn, param) end
end
-- apply function to a window with a timer
function timewin(fn, param)
return timer.new(0.05, function() dowin(fn, param) end)
end
local function app(name)
return function()
application.launchorfocus(name)
end
end
-- keyboard modifier for bindings
local hyper = { "shift", "alt", "cmd" }
local mod1 = { "cmd", "ctrl" }
local mod2 = { "cmd", "alt" }
local mod3 = { "cmd", "shift" }
-- local mod4 = { "alt", "shift" }
-- basic bindings
hotkey.bind(mod2, "c", bindwin(ext.win.center))
hotkey.bind(mod2, "f", bindwin(ext.win.full))
hotkey.bind(mod2, "w", bindwin(ext.win.throw, "prev"))
hotkey.bind(mod2, "e", bindwin(ext.win.throw, "next"))
-- push to edges and nudge
fnutils.each({ "up", "down", "left", "right" }, function(direction)
local nudge = timewin(ext.win.nudge, direction)
hotkey.bind(mod1, direction, bindwin(ext.win.pushandnudge, direction))
hotkey.bind(mod2, direction, bindwin(ext.win.send, direction))
hotkey.bind(hyper, direction, function() nudge:start() end, function() nudge:stop() end)
end)
-- set window sizes
fnutils.each({
{ key = "1", w = 1400, h = 940 },
{ key = "2", w = 980, h = 920 },
{ key = "3", w = 800, h = 880 },
{ key = "4", w = 800, h = 740 },
{ key = "5", w = 700, h = 740 },
{ key = "6", w = 850, h = 620 },
{ key = "7", w = 770, h = 470 }
}, function(object)
hotkey.bind(mod1, object.key, bindwin(ext.win.size, { w = object.w, h = object.h }))
end)
hotkey.bind(hyper, 'D', opendictionary)
hotkey.bind(hyper, 'R', mjolnir.reload)
hotkey.bind(mod1, 'E', app("iTerm"))
hotkey.bind(mod1, 'W', app("Google Chrome"))
-- hotkey.bind(mod1, 'O', app("OmniFocus"))
hotkey.bind(mod1, 'K', app("Flowdock"))
alert.show("Mjolnir, at your service.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment