Created
May 18, 2014 23:48
-
-
Save tmandry/e5c79c10166495429f3b to your computer and use it in GitHub Desktop.
i3-like Phoenix configuration: focusing, fullscreen, marks
This file contains 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
// Move windows between monitors, taken from jakemcc's config | |
function moveToScreen(win, screen) { | |
if (!screen) { | |
return; | |
} | |
var frame = win.frame(); | |
var oldScreenRect = win.screen().frameWithoutDockOrMenu(); | |
var newScreenRect = screen.frameWithoutDockOrMenu(); | |
var xRatio = newScreenRect.width / oldScreenRect.width; | |
var yRatio = newScreenRect.height / oldScreenRect.height; | |
win.setFrame({ | |
x: (Math.round(frame.x - oldScreenRect.x) * xRatio) + newScreenRect.x, | |
y: (Math.round(frame.y - oldScreenRect.y) * yRatio) + newScreenRect.y, | |
width: Math.round(frame.width * xRatio), | |
height: Math.round(frame.height * yRatio) | |
}); | |
} | |
function circularLookup(array, index) { | |
if (index < 0) | |
return array[array.length + (index % array.length)]; | |
return array[index % array.length]; | |
} | |
function rotateMonitors(offset) { | |
var win = Window.focusedWindow(); | |
var currentScreen = win.screen(); | |
var screens = [currentScreen]; | |
for (var x = currentScreen.previousScreen(); x != win.screen(); x = x.previousScreen()) { | |
screens.push(x); | |
} | |
screens = _(screens).sortBy(function(s) { return s.frameWithoutDockOrMenu().x; }); | |
var currentIndex = _(screens).indexOf(currentScreen); | |
moveToScreen(win, circularLookup(screens, currentIndex + offset)); | |
} | |
function leftOneMonitor() { | |
rotateMonitors(-1); | |
} | |
function rightOneMonitor() { | |
rotateMonitors(1); | |
} | |
// My additions to Window | |
Window.prototype.push = function(dir) { | |
var topLeft = this.topLeft(); | |
var screenFrame = this.screen().frameWithoutDockOrMenu(); | |
if (dir == 'left') { | |
topLeft.x = screenFrame.x; | |
} else if (dir == 'right') { | |
topLeft.x = (screenFrame.x + screenFrame.width) - this.size().width; | |
} else if (dir == 'top') { | |
topLeft.y = screenFrame.y; | |
} else if (dir == 'bottom') { | |
topLeft.y = (screenFrame.y + screenFrame.height) - this.size().height; | |
} else { | |
api.alert("Unknown direction " + dir); | |
} | |
this.setTopLeft(topLeft); | |
return this; | |
} | |
Window.prototype.toGrid = function(x, y, width, height) { | |
var screen = this.screen().frameWithoutDockOrMenu(); | |
var padding = 0; | |
this.setFrame({ | |
x: Math.round(x * screen.width) + padding + screen.x, | |
y: Math.round(y * screen.height) + padding + screen.y, | |
width: Math.round(width * screen.width) - (2 * padding), | |
height: Math.round(height * screen.height) - (2 * padding) | |
}); | |
return this; | |
} | |
Window.prototype.shift = function(pctX, pctY) { | |
var topLeft = this.topLeft(); | |
var screen = this.screen().frameWithoutDockOrMenu(); | |
topLeft.x += Math.round(pctX * screen.width); | |
topLeft.y += Math.round(pctY * screen.height); | |
this.setTopLeft(topLeft); | |
} | |
Window.prototype.resize = function(pctX, pctY, anchorX, anchorY) { | |
var frame = this.frame(); | |
var screen = this.screen().frameWithoutDockOrMenu(); | |
var adjX = Math.round(pctX * screen.width); | |
var adjY = Math.round(pctY * screen.height); | |
frame.width += adjX; | |
frame.height += adjY; | |
if (anchorX == 'right') { | |
frame.x -= adjX; | |
} | |
if (anchorY == 'bottom') { | |
frame.y -= adjY; | |
} | |
this.setFrame(frame); | |
return this; | |
} | |
var restoreFrames = {}; | |
Window.prototype.toggleFullscreen = function() { | |
var screen = this.screen().frameWithoutDockOrMenu(); | |
if (!_.isEqual(this.frame(), screen)) { | |
restoreFrames[this.title()] = this.frame(); | |
this.setFrame(screen); | |
} else { | |
this.setFrame(restoreFrames[this.title()]); | |
} | |
} | |
Window.prototype.forceFocusWindow = function() { | |
// If the window is on another screen and another window from the same app is on this screen, | |
// OSX will focus the window on this screen. Once focused, another invocation will go to the | |
// correct window, however. This forces the correct behavior. | |
while (!windowEq(Window.focusedWindow(), this)) this.focusWindow(); | |
} | |
function windowEq(w1, w2) { | |
return w1.title() == w2.title() && _.isEqual(w1.frame(), w2.frame()); | |
} | |
// Marks | |
markBindings = []; | |
function createMarkBindings() { | |
NAMES = "abcdefghijklmnopqrstuvwxyz'"; | |
markBindings = _.map(NAMES, function(name) { | |
binding = api.bind(name, [], function() { markCallback(name); }); | |
return binding; | |
}); | |
markBindings.push(api.bind('escape', [], cancelMarkOp)); | |
disableMarkBindings(); | |
} | |
function enableMarkBindings() { | |
_.each(markBindings, function(binding) { binding.enable(); }); | |
} | |
function disableMarkBindings() { | |
_.each(markBindings, function(binding) { binding.disable(); }); | |
} | |
createMarkBindings(); | |
markOp = null; | |
marks = {}; | |
function beginMarkOp(opName) { | |
markOp = opName; | |
enableMarkBindings(); | |
} | |
function cancelMarkOp() { | |
markOp = null; | |
disableMarkBindings(); | |
} | |
function markCallback(name) { | |
disableMarkBindings(); | |
if (markOp == 'name') { | |
nameMark(name); | |
} else if (markOp == 'focus') { | |
focusMark(name); | |
} | |
markOp = null; | |
} | |
function nameMark(name) { | |
var win = Window.focusedWindow(); | |
marks[name] = win; | |
api.alert(win.app().title() + " / " + win.title() + " → " + name, 0.8); | |
} | |
function focusMark(name) { | |
if (name == "'") { | |
focusLastWindow(); | |
return; | |
} | |
mark = marks[name]; | |
if (!mark) return; | |
mark.forceFocusWindow(); | |
} | |
function focusLastWindow() { | |
// TODO: Work around problem of "illegitimate focusings" that happen during forceFocusWindow being | |
// focused as the last window. | |
Window.visibleWindowsMostRecentFirst()[1].forceFocusWindow(); | |
} | |
// Returns the window from the list of windows with the title title, if that title is unique. | |
// Otherwise, returns undefined. | |
// function findByUniqueTitle(windows, title) { | |
// var results = _.find(windows, function(w){ return w.title() == title; }); | |
// if (results.length != 1) return undefined; | |
// return results[0]; | |
// } | |
// Window.findByUniqueTitle = function(title) { | |
// return findByUniqueTitle(Window.allWindows(), title); | |
// } | |
// function nameMark(name) { | |
// var win = Window.focusedWindow(); | |
// marks[name] = { | |
// app: win.app().title(), | |
// title: win.title(), | |
// topLeft: win.topLeft(), | |
// size: win.size() | |
// }; | |
// api.alert(name + ": " + win.app().title() + " / " + win.title()); | |
// } | |
// function focusMark(name) { | |
// mark = marks[name]; | |
// if (!mark) return; | |
// var app = _.find(App.runningApps(), function(app){ return app.title() == mark.app; }); | |
// if (!app) return; | |
// var windows = app.allWindows(); | |
// // First try to match by title, then by location, then by window size. | |
// var result = findByUniqueTitle(windows, mark.title); | |
// if (!result) result = _.find(windows, function(w){ return _.isEqual(w.topLeft(), mark.topLeft); }); | |
// if (!result) result = _.find(windows, function(w){ return _.isEqual(w.size(), mark.size); }); | |
// if (result) { | |
// // If the window is on another screen and another window from the same app is on this screen, | |
// // OSX will focus the window on this screen. Once focused, another invocation will go to the | |
// // correct window, however. This forces the correct behavior. | |
// while (!windowEq(Window.focusedWindow(), result)) result.focusWindow(); | |
// } | |
// } | |
// Bindings | |
function win() { return Window.focusedWindow(); } | |
var big = 0.10; | |
var sml = 0.05; | |
// Focus | |
api.bind('h', ['alt'], function(){ win().focusWindowLeft(); }); | |
api.bind('l', ['alt'], function(){ win().focusWindowRight(); }); | |
api.bind('k', ['alt'], function(){ win().focusWindowUp(); }); | |
api.bind('j', ['alt'], function(){ win().focusWindowDown(); }); | |
// Movement | |
api.bind('h', ['alt', 'shift', 'cmd', 'ctrl'], leftOneMonitor); | |
api.bind('l', ['alt', 'shift', 'cmd', 'ctrl'], rightOneMonitor); | |
api.bind('h', ['alt', 'shift'], function(){ win().shift(-sml, 0); }) | |
api.bind('l', ['alt', 'shift'], function(){ win().shift(+sml, 0); }) | |
api.bind('k', ['alt', 'shift'], function(){ win().shift(0, -sml); }) | |
api.bind('j', ['alt', 'shift'], function(){ win().shift(0, +sml); }) | |
api.bind('h', ['alt', 'shift', 'cmd'], function(){ win().push('left') }); | |
api.bind('l', ['alt', 'shift', 'cmd'], function(){ win().push('right') }); | |
api.bind('k', ['alt', 'shift', 'cmd'], function(){ win().push('top') }); | |
api.bind('j', ['alt', 'shift', 'cmd'], function(){ win().push('bottom') }); | |
api.bind('h', ['alt', 'shift', 'ctrl'], function(){ win().toGrid(0.0, 0.0, 0.5, 1.0) }); | |
api.bind('l', ['alt', 'shift', 'ctrl'], function(){ win().toGrid(0.5, 0.0, 0.5, 1.0) }); | |
api.bind('y', ['alt', 'shift', 'ctrl'], function(){ win().toGrid(0.0, 0.0, 0.5, 0.5) }); | |
api.bind('p', ['alt', 'shift', 'ctrl'], function(){ win().toGrid(0.5, 0.0, 0.5, 0.5) }); | |
api.bind('.', ['alt', 'shift', 'ctrl'], function(){ win().toGrid(0.5, 0.5, 0.5, 0.5) }); | |
api.bind('b', ['alt', 'shift', 'ctrl'], function(){ win().toGrid(0.0, 0.5, 0.5, 0.5) }); | |
// Resizing | |
// top/left edges | |
api.bind('left', ['alt', 'ctrl'], function(){ win().resize(+big, 0.0, 'right', 'bottom') }); | |
api.bind('right', ['alt', 'ctrl'], function(){ win().resize(-big, 0.0, 'right', 'bottom') }); | |
api.bind('up', ['alt', 'ctrl'], function(){ win().resize(0.0, +big, 'right', 'bottom') }); | |
api.bind('down', ['alt', 'ctrl'], function(){ win().resize(0.0, -big, 'right', 'bottom') }); | |
api.bind('left', ['alt', 'ctrl', 'shift'], function(){ win().resize(+sml, 0.0, 'right', 'bottom') }); | |
api.bind('right', ['alt', 'ctrl', 'shift'], function(){ win().resize(-sml, 0.0, 'right', 'bottom') }); | |
api.bind('up', ['alt', 'ctrl', 'shift'], function(){ win().resize(0.0, +sml, 'right', 'bottom') }); | |
api.bind('down', ['alt', 'ctrl', 'shift'], function(){ win().resize(0.0, -sml, 'right', 'bottom') }); | |
// bottom/right edges | |
api.bind('left', ['alt', 'cmd'], function(){ win().resize(-big, 0.0, 'left', 'top') }); | |
api.bind('right', ['alt', 'cmd'], function(){ win().resize(+big, 0.0, 'left', 'top') }); | |
api.bind('up', ['alt', 'cmd'], function(){ win().resize(0.0, -big, 'left', 'top') }); | |
api.bind('down', ['alt', 'cmd'], function(){ win().resize(0.0, +big, 'left', 'top') }); | |
api.bind('left', ['alt', 'cmd', 'shift'], function(){ win().resize(-sml, 0.0, 'left', 'top') }); | |
api.bind('right', ['alt', 'cmd', 'shift'], function(){ win().resize(+sml, 0.0, 'left', 'top') }); | |
api.bind('up', ['alt', 'cmd', 'shift'], function(){ win().resize(0.0, -sml, 'left', 'top') }); | |
api.bind('down', ['alt', 'cmd', 'shift'], function(){ win().resize(0.0, +sml, 'left', 'top') }); | |
// Misc | |
api.bind('f', ['alt'], function(){ win().toggleFullscreen() }); | |
// Marks | |
api.bind('m', ['alt'], function(){ beginMarkOp('name'); }); | |
api.bind("'", ['alt'], function(){ beginMarkOp('focus'); }); | |
// TODO | |
// 'jigsaw' move | |
// remember mouse position |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment