Created
January 17, 2022 19:02
-
-
Save frankpf/6b60040643a1bddbe2413f89526d2137 to your computer and use it in GitHub Desktop.
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
Phoenix.set({ | |
openAtLogin: true, | |
}) | |
Key.on('return', ['option'], () => { | |
createTerminal() | |
}) | |
Key.on('y', ['option'], () => { | |
Modal.build({ | |
text: 'aaz', | |
origin: { x: 120, y: 120 }, | |
duration: 20, | |
}).show() | |
}) | |
// Focus right window | |
Key.on('l', ['option'], () => { | |
const window = currentWindow() | |
const {width: screenWidth} = Screen.main().visibleFrame() | |
const {x, y} = window.topLeft() | |
const {width: windowWidth} = window.size() | |
const end = x + windowWidth | |
// Try to find a window to the right of {end} | |
let otherWindow = undefined | |
for (let i = 1; i < (screenWidth-end); i = i + 10) { | |
Phoenix.log(`trying ${i} at ${end+i}, ${y}`) | |
const w = Window.at({ x: end + i, y: y + 10 }) | |
if (w !== undefined && w.app().name() !== 'Finder') { | |
otherWindow = w | |
break | |
} | |
} | |
if (otherWindow === undefined) { | |
return | |
} | |
otherWindow.focus() | |
}) | |
// Focus left window | |
Key.on('h', ['option'], () => { | |
const window = currentWindow() | |
const {width: screenWidth} = Screen.main().visibleFrame() | |
const {x, y} = window.topLeft() | |
const {width: windowWidth} = window.size() | |
// Try to find a window to the left of {x} | |
let otherWindow = undefined | |
for (let i = x-1; i > 0; i--) { | |
const w = Window.at({ x: i, y: y + 10 }) | |
if (w !== undefined && w.app().name() !== 'Finder') { | |
otherWindow = w | |
break | |
} | |
} | |
if (otherWindow === undefined) { | |
return | |
} | |
otherWindow.focus() | |
}) | |
// Focus bottom window | |
Key.on('j', ['option'], () => { | |
const window = currentWindow() | |
const {height: screenHeight} = Screen.main().visibleFrame() | |
const {x, y} = window.topLeft() | |
const {height: windowHeight} = window.size() | |
const bottom = y + windowHeight | |
Phoenix.log(`bottom: ${bottom}, height: ${screenHeight}, x=${x}, y=${y}`) | |
// Try to find a window below {bottom} | |
let otherWindow = undefined | |
for (let i = bottom; i < screenHeight; i++) { | |
const w = Window.at({ x: x + 10, y: y + i }) | |
if (w !== undefined && w.app().name() !== 'Finder') { | |
otherWindow = w | |
break | |
} | |
} | |
if (otherWindow === undefined) { | |
return | |
} | |
otherWindow.focus() | |
}) | |
// Focus top window | |
Key.on('k', ['option'], () => { | |
const window = currentWindow() | |
const {width: screenWidth} = Screen.main().visibleFrame() | |
const {x, y} = window.topLeft() | |
const {width: windowWidth} = window.size() | |
// Try to find a window to the top of {y} | |
let otherWindow = undefined | |
for (let i = y-1; i > 0; i--) { | |
const w = Window.at({ x: x + 10, y: i }) | |
if (w !== undefined && w.app().name() !== 'Finder') { | |
otherWindow = w | |
break | |
} | |
} | |
if (otherWindow === undefined) { | |
return | |
} | |
otherWindow.focus() | |
}) | |
const createTerminal = () => | |
osascript(` | |
if application "iTerm" is running then | |
tell application "iTerm" | |
create window with default profile | |
end tell | |
else | |
activate application "iTerm" | |
end if | |
`) | |
function moveWindowToWorkspace(window, workspaceIdx) { | |
const targetSpace = Space.all()[workspaceIdx] | |
const currentSpace = Space.active() | |
if (currentSpace.isEqual(targetSpace)) { | |
return | |
} | |
targetSpace.addWindows([window]) | |
currentSpace.removeWindows([window]) | |
} | |
const NUM_SPACES = 8 | |
for (let i = 1; i <= NUM_SPACES; i++) { | |
// Go to workspace $i is implemented in System Prefs/Keyboard/Mission Control | |
// Move current window to workspace $i | |
Key.on(i.toString(), ['option', 'shift'], () => { | |
// Move window | |
const window = currentWindow() | |
moveWindowToWorkspace(window, i-1) | |
// Focus first window in space | |
const spaceWindows = getRealWindows(Space.active()) | |
if (spaceWindows.length == 0) { | |
return | |
} | |
spaceWindows[0].focus() | |
}) | |
} | |
function windowsInSpace(space) { | |
const windows = space.windows() | |
return filterRealWindows(windows) | |
} | |
function osascript(script) { | |
Task.run('/usr/bin/osascript', ['-e', script]) | |
} | |
Key.on('q', ['option', 'shift'], () => { | |
Window.focused().close() | |
const spaceWindows = getRealWindows(Space.active()) | |
if (spaceWindows.length == 0) { | |
return | |
} | |
spaceWindows[0].focus() | |
}) | |
//Key.on('g', ['option', 'shift'], () => { | |
// const window = currentWindow() | |
// const currSpace = Space.active() | |
// const spaces = Space.all() | |
// Phoenix.log(currSpace.next().hash()) | |
// Phoenix.log(currSpace.next().next().hash()) | |
// Phoenix.log(currSpace.next().next().next().hash()) | |
// Phoenix.log(currSpace.next().next().next().next().hash()) | |
// Phoenix.log( Screen.main().spaces().length) | |
// | |
// createSpace2() | |
// | |
//}) | |
Key.on('f', ['option'], () => { | |
const {width, height} = Screen.main().visibleFrame() | |
const window = currentWindow() | |
window.setTopLeft({ x: 0, y: 0 }) | |
window.setSize({ width, height }) | |
}) | |
// Switch layout | |
// 2 windows | |
// | A | B | | |
// | | | | |
// 3 windows | |
// | A | B | | |
// | | C | | |
// | |
// 4 windows | |
// | A | B | | |
// | D | C | | |
// | |
class X { | |
constructor(lol) { | |
this.lol = lol | |
} | |
} | |
class Layout { | |
constructor(windows) { | |
windows.sort((a, b) => a.hash() - b.hash()) | |
this.windows = windows | |
this.focusCounter = 0 | |
} | |
serialize() { | |
const className = this.__proto__.constructor.name | |
const windowHashes = this.windows.map(_ => _.hash()) | |
const windowHashesStr = windowHashes.join(',') | |
return `${className}:${windowHashesStr}` | |
} | |
rotate() { | |
const lastWindow = this.windows.pop() | |
const l = this.windows.unshift(lastWindow) | |
Phoenix.log(lastWindow.hash(), l, this.windows.map(_ => _.hash())) | |
this.arrange() | |
} | |
moveToLeft(window) { | |
const {y} = window.topLeft() | |
window.setTopLeft({ x: 0, y }) | |
} | |
moveToRight(window) { | |
const {y} = window.topLeft() | |
const {width} = this.screenSize() | |
window.setTopLeft({ x: width / 2, y }) | |
} | |
moveToBottom(window) { | |
const {x} = window.topLeft() | |
const {height} = this.screenSize() | |
window.setTopLeft({ x, y: height / 2 }) | |
} | |
moveToTop(window) { | |
const {x} = window.topLeft() | |
window.setTopLeft({ x, y: 0 }) | |
} | |
maximize(window) { | |
const {height: screenHeight, width: screenWidth} = this.screenSize() | |
const {width} = window.size() | |
window.setSize({ width: screenWidth, height: screenHeight }) | |
} | |
halveHorizontally(window) { | |
const {height: screenHeight} = this.screenSize() | |
const {width} = window.size() | |
window.setSize({ width, height: screenHeight / 2 }) | |
} | |
halveVertically(window) { | |
const {width: screenWidth} = this.screenSize() | |
const {height} = window.size() | |
window.setSize({ width: screenWidth / 2, height }) | |
} | |
screenSize() { | |
return Screen.main().visibleFrame() | |
} | |
focusNext() { | |
this.focusCounter = (this.focusCounter + 1) % this.windows.length | |
this.windows[this.focusCounter].focus() | |
} | |
static deserialize(input) { | |
const [className, windowHashesStr] = input.split(':') | |
const classRef = GLOBAL[className] | |
const windowHashes = new Set(windowHashesStr.split(',').map(_ => Number(_))) | |
let windows = [] | |
let allWindows = Window.all() | |
for (const window of allWindows) { | |
if (windowHashes.has(window.hash())) { | |
windows.push(window) | |
} | |
} | |
const obj = new classRef(windows) | |
return obj | |
} | |
} | |
class MaximizedLayout extends Layout { | |
constructor(windows) { | |
super(windows) | |
} | |
arrange() { | |
Phoenix.log(`arranging MaximizedLayout ${this.windows.map(_ => _.app().name())}`) | |
const {width, height} = this.screenSize() | |
Phoenix.log(`arranging MaximizedLayout ${this.windows.map(_ => _.app().name())} with width ${width}`) | |
for (const window of this.windows) { | |
window.setTopLeft({ | |
x: 0, y: 0, | |
}) | |
window.setSize({ | |
width: width, | |
height: height, | |
}) | |
} | |
} | |
} | |
class TwoWindowLayout extends Layout { | |
constructor(windows) { | |
super(windows) | |
} | |
arrange() { | |
moveWindowToLeft(this.windows[0]) | |
moveWindowToRight(this.windows[1]) | |
} | |
} | |
class ThreeWindowLayout extends Layout { | |
constructor(windows) { | |
super(windows) | |
} | |
arrange() { | |
// | 0 | 1 | | |
// | | 2 | | |
moveWindowToLeft(this.windows[0]) | |
moveWindowToRight(this.windows[1]) | |
moveWindowToRight(this.windows[2]) | |
const {width, height} = Screen.main().visibleFrame() | |
this.windows[1].setSize({ | |
height: height / 2, | |
width: width / 2, | |
}) | |
this.windows[1].setTopLeft({ | |
x: width / 2, | |
y: 0, | |
}) | |
this.windows[2].setSize({ | |
height: height / 2, | |
width: width / 2, | |
}) | |
this.windows[2].setTopLeft({ | |
x: width / 2, | |
y: 25 + (height / 2), | |
}) | |
} | |
} | |
class FourWindowLayout extends Layout { | |
constructor(windows) { | |
super(windows) | |
} | |
arrange() { | |
// | 0 | 1 | | |
// | 3 | 2 | | |
moveWindowToLeft(this.windows[0]) | |
moveWindowToLeft(this.windows[3]) | |
moveWindowToRight(this.windows[1]) | |
moveWindowToRight(this.windows[2]) | |
const {width, height} = this.screenSize() | |
// Halve windows | |
this.windows[0].setSize({ | |
height: height / 2, | |
width: width / 2, | |
}) | |
this.windows[1].setSize({ | |
height: height / 2, | |
width: width / 2, | |
}) | |
this.windows[2].setSize({ | |
height: height / 2, | |
width: width / 2, | |
}) | |
this.windows[3].setSize({ | |
height: height / 2, | |
width: width / 2, | |
}) | |
// Set positions for bottom windows | |
this.windows[3].setTopLeft({ | |
x: 0, | |
y: 25 + (height / 2), | |
}) | |
this.windows[2].setTopLeft({ | |
x: width / 2, | |
y: 25 + (height / 2), | |
}) | |
} | |
} | |
const GLOBAL = { | |
'FourWindowLayout': FourWindowLayout, | |
'ThreeWindowLayout': ThreeWindowLayout, | |
'TwoWindowLayout': TwoWindowLayout, | |
'OneWindowLayout': MaximizedLayout, | |
} | |
const MEMORY = { layout: undefined } | |
function getRealWindows(screenOrSpace) { | |
const allWindows = screenOrSpace.windows() | |
return filterRealWindows(allWindows) | |
} | |
const IGNORED_APPS = new Set(['Finder', 'Notification Center']) | |
function filterRealWindows(allWindows) { | |
const realWindows = [] | |
for (const window of allWindows) { | |
if (!IGNORED_APPS.has(window.app().name())) { | |
realWindows.push(window) | |
} | |
} | |
return realWindows | |
} | |
// Switch layout | |
// | |
function getLayoutForScreen(screen) { | |
const windows = getRealWindows(screen) | |
const classRef = LAYOUT_MAP[windows.length] || MaximizedLayout | |
return new classRef(windows) | |
} | |
Key.on('i', ['shift', 'option'], () => { | |
const windows = getRealWindows(Space.active()) | |
Phoenix.notify(windows.map(_ => _.app().name()).join(' | ')) | |
}) | |
function arrayEqual(arr1, arr2) { | |
if (arr1 === arr2) { | |
return true | |
} | |
if (arr1.length !== arr2.length) { | |
return false | |
} | |
for (let i = 0; i < arr1.length; i++) { | |
if (arr1[i] !== arr2[i]) { | |
return false | |
} | |
} | |
return true | |
} | |
function setEqual(set1, set2) { | |
if (set1.size !== set2.size) { | |
return false | |
} | |
for (const item of set1) { | |
if (!set2.has(item)) { | |
return false | |
} | |
} | |
return true | |
} | |
function getLayout(screen) { | |
const key = `screen/${screen.hash()}:layout` | |
let windows | |
if (MEMORY[key] === undefined) { | |
Phoenix.log('Layout not found, updating...') | |
MEMORY[key] = getLayoutForScreen(screen) | |
windows = MEMORY[key].windows | |
} else { | |
windows = getRealWindows(screen) | |
const windowSet = new Set(windows.map(_ => _.hash())) | |
const layoutWindowSet = new Set(MEMORY[key].windows.map(_ => _.hash())) | |
Phoenix.log(`windowSet=${Array.from(windowSet)}, layoutWindowSet=${Array.from(layoutWindowSet)}`) | |
if (setEqual(windowSet, layoutWindowSet)) { | |
Phoenix.log('Layout found AND up-to-date') | |
} else { | |
Phoenix.log('Layout found but outdated, updating...') | |
MEMORY[key] = getLayoutForScreen(screen) | |
} | |
} | |
return MEMORY[key] | |
} | |
// Switch layout | |
Key.on('s', ['option'], () => { | |
const screen = Screen.main() | |
const layout = getLayout(screen) | |
Phoenix.log(`Switching to layout ${layout.__proto__.constructor.name}`) | |
layout.arrange() | |
}) | |
// Rotate windows | |
Key.on('r', ['option'], () => { | |
const screen = Screen.main() | |
const layout = getLayout(screen) | |
layout.rotate() | |
}) | |
// Focus next | |
Key.on('n', ['option'], () => { | |
const screen = Screen.main() | |
const layout = getLayout(screen) | |
layout.focusNext() | |
}) | |
const LAYOUT_MAP = { | |
1: MaximizedLayout, | |
2: TwoWindowLayout, | |
3: ThreeWindowLayout, | |
4: FourWindowLayout | |
} | |
function moveWindowToRight(window) { | |
const {width, height} = Screen.main().visibleFrame() | |
window.setTopLeft({ | |
x: width / 2, | |
y: 0, | |
}) | |
window.setSize({ width: width / 2, height }) | |
} | |
function moveWindowToLeft(window) { | |
const {width, height} = Screen.main().visibleFrame() | |
window.setTopLeft({ x: 0, y : 0 }) | |
window.setSize({ width: width / 2, height }) | |
} | |
Key.on('e', ['option'], () => { | |
const {width, height} = Screen.main().visibleFrame() | |
const window = currentWindow() | |
const key = `window:${window.hash()}` | |
const val = Storage.get(key) | |
if (val === undefined || val === 'right') { | |
window.setTopLeft({ | |
x: width / 2, | |
y: 0, | |
}) | |
Storage.set(key, 'left') | |
} else { | |
window.setTopLeft({ x: 0, y : 0 }) | |
Storage.set(key, 'right') | |
} | |
window.setSize({ width: width / 2, height }) | |
}) | |
// can also edit Library/Preferences/com.apple.spaces.plist and killall Finder | |
//https://stackoverflow.com/questions/9606221/how-can-i-programmatically-add-a-space-to-mission-control?rq=1 | |
function createSpace() { | |
osascript(` | |
do shell script "open -a 'Mission Control'" | |
delay 0.5 | |
tell application "System Events" to ¬ | |
click (every button whose value of ¬ | |
attribute "AXDescription" is "add desktop") of ¬ | |
group 2 of group 1 of group 1 of process "Dock" | |
delay 0.5 | |
tell application "System Events" to key code 53 | |
`) | |
} | |
function createSpace2() { | |
osascript(` | |
do shell script "open -b 'com.apple.exposelauncher'" | |
delay 0.5 | |
tell application id "com.apple.systemevents" | |
tell (every application process ¬ | |
whose bundle identifier = "com.apple.dock") to ¬ | |
click (button 1 of group 2 of group 1 of group 1) | |
delay 0.5 | |
key code 53 -- esc key | |
end tell | |
`) | |
} | |
function currentWindow() { | |
const currentWindow = Window.focused() | |
if (currentWindow !== undefined) { | |
return currentWindow | |
} | |
return App.focused().mainWindow() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment