--[[ | |
# Install dependencies. | |
brew update | |
brew install lua | |
brew install luarocks | |
brew install blueutil | |
wget https://github.com/sdegutis/mjolnir/releases/download/0.4.3/Mjolnir-0.4.3.tgz | |
mkdir -p ~/.luarocks | |
mkdir -p ~/.mjolnir/ | |
echo 'rocks_servers = { "http://rocks.moonscript.org" }' > ~/.luarocks/config.lua | |
luarocks install mjolnir.application | |
luarocks install mjolnir.hotkey | |
luarocks install mjolnir.screen | |
luarocks install mjolnir.sk.transform | |
luarocks install mjolnir._asm.timer | |
luarocks install mjolnir._asm.ui.notification | |
#Move this file to ~/.mjolnir/init.lua | |
]]-- | |
local hotkey = require "mjolnir.hotkey" | |
local window = require "mjolnir.window" | |
local fnutils = require "mjolnir.fnutils" | |
local application = require "mjolnir.application" | |
local timer = require "mjolnir._asm.timer" | |
local notification = require "mjolnir._asm.ui.notification" | |
local transform = require "mjolnir.sk.transform" | |
local alert = require "mjolnir.alert" | |
-- extensions | |
local ext = { | |
frame = {}, | |
win = {}, | |
app = {}, | |
utils = {} | |
} | |
-- window extension settings | |
ext.win.margin = 2 | |
ext.win.movespeed = 30 | |
ext.win.positions = {} | |
ext.win.animate = false | |
ext.win.fixenabled = false | |
-- returns frame pushed to screen edge | |
function ext.frame.push(screen, direction) | |
-- Define heights, widths, positions. | |
local short = screen.h / 2 - ext.win.margin | |
local tall = screen.h - ext.win.margin * (2 - 1 / 4) | |
local wide = screen.w - ext.win.margin * 2 | |
local narrow = screen.w / 2 - ext.win.margin * (2 - 1 / 4) | |
local screenx = ext.win.margin + screen.x | |
local screenmiddlex = ext.win.margin / 2 + screen.w / 2 + screen.x | |
local screeny = ext.win.margin + screen.y | |
local screenmiddley = ext.win.margin * 3 / 4 + screen.h / 2 + screen.y | |
local frames = { | |
[ "full"] = function() return { x = screenx, y = screeny, w = wide, h = tall } end, | |
[ "upleft"] = function() return { x = screenx, y = screeny, w = narrow, h = short } end, | |
[ "upright"] = function() return { x = screenmiddlex, y = screeny, w = narrow, h = short } end, | |
[ "downright"] = function() return { x = screenmiddlex, y = screenmiddley, w = narrow, h = short } end, | |
[ "downleft"] = function() return { x = screenx, y = screenmiddley, w = narrow, h = short } end, | |
[ "up" ] = function() return { x = screenx, y = screeny, w = wide, h = short } end, | |
[ "down" ] = function() return { x = screenx, y = screenmiddley, w = wide, h = short } end, | |
[ "left" ] = function() return { x = screenx, y = screeny, w = narrow, h = tall } end, | |
[ "right" ] = function() return { x = screenmiddlex, y = screeny, w = narrow, h = tall } end | |
} | |
return frames[direction]() | |
end | |
-- throw to next screen, center and fit | |
function ext.win.next(win,direction) | |
local screen = win:screen():next():frame() | |
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() | |
ext.win.center(win) | |
end | |
-- throw to next screen, center and fit | |
function ext.win.previous(win) | |
local screen = win:screen():previous():frame() | |
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() | |
ext.win.center(win) | |
end | |
-- centers window | |
function ext.win.center(win) | |
local screen = win:screen():frame() | |
local frame = win:frame() | |
frame = ext.frame.center(frame, screen) | |
ext.win.set(win, 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 | |
-- returns frame moved by ext.win.margin | |
function ext.frame.nudge(frame, screen, direction) | |
local modifyframe = { | |
[ "up" ] = function(frame) | |
frame.y = math.max(screen.y + ext.win.movespeed, frame.y - ext.win.movespeed) | |
return frame | |
end, | |
[ "down" ] = function(frame) | |
frame.y = math.min(screen.y + screen.h - frame.h - ext.win.movespeed * 3 / 4, frame.y + ext.win.movespeed) | |
return frame | |
end, | |
[ "left" ] = function(frame) | |
frame.x = math.max(screen.x + ext.win.movespeed, frame.x - ext.win.movespeed) | |
return frame | |
end, | |
[ "right" ] = function(frame) | |
frame.x = math.min(screen.x + screen.w - frame.w - ext.win.movespeed, frame.x + ext.win.movespeed) | |
return frame | |
end | |
} | |
return modifyframe[direction](frame) | |
end | |
-- returns frame sent to screen edge | |
function ext.frame.send(frame, screen, direction) | |
local modifyframe = { | |
[ "up" ] = function(frame) | |
frame.y = screen.y + ext.win.margin | |
return frame | |
end, | |
[ "down" ] = function(frame) | |
frame.y = screen.y + screen.h - frame.h - ext.win.margin * 3 / 4 | |
return frame | |
end, | |
[ "left" ] = function(frame) | |
frame.x = screen.x + ext.win.margin | |
return frame | |
end, | |
[ "right" ] = function(frame) | |
frame.x = screen.x + screen.w - frame.w - ext.win.margin | |
return frame | |
end | |
} | |
return modifyframe[direction](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 - 1 / 4)) | |
return frame | |
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 = win:screen():frame() | |
local frame = win:frame() | |
if (frame.h > (screen.h - ext.win.margin * (2 - 1 / 4))) 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 = win:screen():frame() | |
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 = win:screen():frame() | |
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 = win:screen():frame() | |
local frame = win:frame() | |
frame = ext.frame.send(frame, screen, direction) | |
ext.win.fix(win) | |
ext.win.set(win, frame) | |
end | |
-- set window size and center | |
function ext.win.size(win, size) | |
local screen = win:screen():frame() | |
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 | |
-- END Window manager. | |
-- smart browser launch or focus | |
ext.app.browser = function() | |
local browsers = { "Google Chrome", "Safari"} | |
local runningapps = application.runningapplications() | |
local runningbrowsers = fnutils.map(browsers, function(browser) | |
return fnutils.find(runningapps, function(app) | |
return app:title() == browser | |
end) | |
end) | |
if #runningbrowsers > 0 then | |
runningbrowsers[1]:activate() | |
else | |
application.launchorfocus(browsers[1]) | |
end | |
end | |
-- exec command | |
ext.utils.exec = function(command) | |
local handle = io.popen(os.getenv("SHELL") .. " -l -i -c \"" .. command .. "\"", "r") | |
local output = handle:read("*all") | |
handle:close() | |
return output | |
end | |
-- toggle bluetooth | |
ext.utils.togglebluetooth = function() | |
local status = string.len(ext.utils.exec("blueutil | grep 'Power: 1'")) > 0 | |
local command = "blueutil power " .. (status and "0" or "1") | |
notification.show("Bluetooth", "Turned " .. (status and "off" or "on")) | |
ext.utils.exec(command) | |
end | |
-- toggle wifi | |
ext.utils.togglewifi = function() | |
local status = string.len(ext.utils.exec("networksetup -getairportpower en0 | grep On")) > 0 | |
local command = "networksetup -setairportpower en0 " .. (status and "off" or "on") | |
notification.show("Wireless", "Turned " .. (status and "off" or "on")) | |
ext.utils.exec(command) | |
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.02, function() | |
dowin(fn, param) | |
end) | |
end | |
-- keyboard modifier for bindings | |
local ctrlaltcmd = {"cmd", "alt", "ctrl"} | |
local shiftctrlalt = {"shift", "ctrl", "alt"} | |
local ctrlalt = {"ctrl", "alt"} | |
local cmd = {"cmd"} | |
local cmdalt = {"cmd", "alt"} | |
local cmdctrl = { "cmd", "ctrl" } | |
local cmdaltshift = { "cmd", "alt", "shift" } | |
-- basic bindings | |
hotkey.bind(ctrlaltcmd, "c", bindwin(ext.win.center)) | |
hotkey.bind(ctrlaltcmd, "m", bindwin(ext.win.push, "full")) | |
-- hotkey.bind(ctrlaltcmd, "s", bindwin(ext.win.pos, "update")) | |
-- hotkey.bind(ctrlaltcmd, "r", bindwin(ext.win.pos, "load")) | |
hotkey.bind(ctrlaltcmd, "r", function() | |
mjolnir.reload(); | |
alert.show("Config loaded") | |
end) | |
-- Send to left/right Screen . | |
hotkey.bind(ctrlalt, "right", bindwin(ext.win.next)) | |
hotkey.bind(ctrlalt, "left", bindwin(ext.win.previous)) | |
-- Send to screen corners | |
hotkey.bind(shiftctrlalt,"left",bindwin(ext.win.push, "upleft")) | |
hotkey.bind(shiftctrlalt,"up",bindwin(ext.win.push, "upright")) | |
hotkey.bind(shiftctrlalt,"down",bindwin(ext.win.push, "downleft")) | |
hotkey.bind(shiftctrlalt,"right",bindwin(ext.win.push, "downright")) | |
-- push to edges | |
fnutils.each({ "up", "down", "left", "right" }, function(direction) | |
local nudge = timewin(ext.win.nudge, direction) | |
hotkey.bind(ctrlaltcmd, direction, bindwin(ext.win.push, direction)) | |
end) | |
-- set window sizes | |
fnutils.each({ | |
{ key = "1", w = 1920, h = 1500 }, | |
{ key = "2", w = 1400, h = 940 }, | |
{ key = "3", w = 980, h = 920 }, | |
{ key = "4", w = 800, h = 880 }, | |
{ key = "5", w = 800, h = 740 } | |
}, function(object) | |
hotkey.bind(cmdctrl, object.key, bindwin(ext.win.size, { w = object.w, h = object.h })) | |
end) | |
-- launch or focus browser in a smart way | |
hotkey.bind(ctrlaltcmd, "b", function() ext.app.browser() end) | |
-- toggle bluetooth and wifi | |
hotkey.bind(shiftctrlalt, "b", function() ext.utils.togglebluetooth() end) | |
hotkey.bind(shiftctrlalt, "w", function() ext.utils.togglewifi() end) | |
-- launch and focus applications | |
fnutils.each({ | |
{ key = "c", app = "Calendar" }, | |
{ key = "f", app = "Finder" }, | |
{ key = "t", app = "iterm" } | |
}, function(object) | |
hotkey.bind(shiftctrlalt, object.key, function() application.launchorfocus(object.app) end) | |
end) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment