201 lines
7.5 KiB
JavaScript
Executable File
201 lines
7.5 KiB
JavaScript
Executable File
const fs = require('fs');
|
|
const path = require('path');
|
|
const { execSync } = require('child_process');
|
|
|
|
const HOME = process.env.HOME;
|
|
const LAYOUT_FILE = path.join(HOME, '.hammerspoon/saved_layout.json');
|
|
const CONFIG_FILE = path.join(HOME, '.hammerspoon/config.json');
|
|
const TEMP_JS_FILE = '/tmp/raycast_window_engine.js';
|
|
|
|
let stubbornApps = ["Gemini", "AFFiNE", "Terminal", "System Settings"];
|
|
let ignoreApps = ["TheBoringNotch", "Control Center", "Notification Center", "Dock", "BetterDisplay", "Stats", "DockDoor", "Bartender 6", "Bartender", "Arq", "Arq Agent", "TypeWhisper", "com.typewhisper.mac"];
|
|
|
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
try {
|
|
const cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
if (cfg.stubbornAppsList) stubbornApps = Object.keys(cfg.stubbornAppsList);
|
|
if (cfg.ignoreListItems) ignoreApps = Object.keys(cfg.ignoreListItems);
|
|
} catch (e) {}
|
|
}
|
|
|
|
const action = process.argv[2];
|
|
|
|
function getVisibleWindows() {
|
|
// Queries the global System Events window array directly, replicating hs.window.allWindows()
|
|
const captureScript = `
|
|
var sys = Application('System Events');
|
|
var out = [];
|
|
try {
|
|
var allWins = sys.windows();
|
|
for (var i = 0; i < allWins.length; i++) {
|
|
var win = allWins[i];
|
|
var proc = win.process();
|
|
if (!proc) continue;
|
|
|
|
var procName = proc.name();
|
|
var bundleID = proc.bundleIdentifier() || "";
|
|
var size = win.size();
|
|
var pos = win.position();
|
|
var title = win.title() || "";
|
|
|
|
if (size && size[0] > 10 && size[1] > 10 && pos && pos[0] !== undefined) {
|
|
out.push({
|
|
appName: procName,
|
|
bundleID: bundleID,
|
|
winTitle: title,
|
|
x: Math.floor(pos[0]),
|
|
y: Math.floor(pos[1]),
|
|
w: Math.floor(size[0]),
|
|
h: Math.floor(size[1])
|
|
});
|
|
}
|
|
}
|
|
} catch(e) {}
|
|
JSON.stringify(out);
|
|
`;
|
|
|
|
try {
|
|
fs.writeFileSync(TEMP_JS_FILE, captureScript, 'utf8');
|
|
const rawOutput = execSync(`osascript -l JavaScript ${TEMP_JS_FILE}`).toString().trim();
|
|
if (fs.existsSync(TEMP_JS_FILE)) fs.unlinkSync(TEMP_JS_FILE);
|
|
|
|
if (!rawOutput || rawOutput === "[]") return [];
|
|
return JSON.parse(rawOutput);
|
|
} catch (e) {
|
|
if (fs.existsSync(TEMP_JS_FILE)) fs.unlinkSync(TEMP_JS_FILE);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function saveLayout() {
|
|
const windows = getVisibleWindows().filter(w => w && !ignoreApps.includes(w.appName) && !ignoreApps.includes(w.bundleID));
|
|
if (windows.length === 0) {
|
|
console.log("No active windows detected to save.");
|
|
return;
|
|
}
|
|
|
|
const layout = {
|
|
saveTime: new Date().toLocaleTimeString(),
|
|
windows: windows
|
|
};
|
|
|
|
if (!fs.existsSync(path.dirname(LAYOUT_FILE))) {
|
|
fs.mkdirSync(path.dirname(LAYOUT_FILE), { recursive: true });
|
|
}
|
|
|
|
fs.writeFileSync(LAYOUT_FILE, JSON.stringify(layout, null, 2));
|
|
console.log("Layout Saved Successfully");
|
|
}
|
|
|
|
function restoreLayout() {
|
|
if (!fs.existsSync(LAYOUT_FILE)) {
|
|
console.log("Restore failed: No saved layout found.");
|
|
return;
|
|
}
|
|
|
|
const data = JSON.parse(fs.readFileSync(LAYOUT_FILE, 'utf8'));
|
|
if (!data.windows || data.windows.length === 0) return;
|
|
|
|
const savedByApp = {};
|
|
data.windows.forEach(win => {
|
|
if (ignoreApps.includes(win.appName) || ignoreApps.includes(win.bundleID)) return;
|
|
if (!savedByApp[win.appName]) savedByApp[win.appName] = [];
|
|
savedByApp[win.appName].push(win);
|
|
});
|
|
|
|
Object.keys(savedByApp).forEach(appName => {
|
|
savedByApp[appName].sort((a, b) => {
|
|
if (a.y === b.y) return a.x - b.x;
|
|
return a.y - b.y;
|
|
});
|
|
});
|
|
|
|
let restoreScript = `var sys = Application('System Events');\n`;
|
|
Object.keys(savedByApp).forEach(appName => {
|
|
const savedEntries = savedByApp[appName];
|
|
restoreScript += `
|
|
try {
|
|
if (sys.processes.names().includes("${appName}")) {
|
|
var proc = sys.processes["${appName}"];
|
|
var physicalWins = [];
|
|
var allWins = proc.windows();
|
|
|
|
for (var k = 0; k < allWins.length; k++) {
|
|
try {
|
|
var w = allWins[k];
|
|
var s = w.size();
|
|
if (s && s[0] > 10 && s[1] > 10) {
|
|
physicalWins.push(w);
|
|
}
|
|
} catch(err) {}
|
|
}
|
|
|
|
physicalWins.sort(function(a, b) {
|
|
var posA = a.position(); var posB = b.position();
|
|
if (!posA || !posB) return 0;
|
|
if (posA[1] === posB[1]) return posA[0] - posB[0];
|
|
return posA[1] - posB[1];
|
|
});
|
|
|
|
var savedEntriesForApp = ${JSON.stringify(savedEntries)};
|
|
for (var i = 0; i < Math.min(savedEntriesForApp.length, physicalWins.length); i++) {
|
|
try {
|
|
var winData = savedEntriesForApp[i];
|
|
var targetWin = physicalWins[i];
|
|
|
|
targetWin.position = [winData.x, winData.y];
|
|
targetWin.size = [winData.w, winData.h];
|
|
} catch(err) {}
|
|
}
|
|
}
|
|
} catch(e) {}
|
|
`;
|
|
});
|
|
|
|
try {
|
|
fs.writeFileSync(TEMP_JS_FILE, restoreScript, 'utf8');
|
|
execSync(`osascript -l JavaScript ${TEMP_JS_FILE} 2>/dev/null`);
|
|
|
|
if (data.windows.some(w => stubbornApps.includes(w.appName))) {
|
|
setTimeout(() => {
|
|
try { execSync(`osascript -l JavaScript ${TEMP_JS_FILE} 2>/dev/null`); } catch(e){}
|
|
if (fs.existsSync(TEMP_JS_FILE)) fs.unlinkSync(TEMP_JS_FILE);
|
|
}, 500);
|
|
} else {
|
|
if (fs.existsSync(TEMP_JS_FILE)) fs.unlinkSync(TEMP_JS_FILE);
|
|
}
|
|
} catch (e) {}
|
|
console.log("Layout Restored Successfully");
|
|
}
|
|
|
|
function rescueWindows() {
|
|
let rescueScript = `
|
|
var sys = Application('System Events');
|
|
var allWins = sys.windows();
|
|
var stagger = 0;
|
|
for (var i = 0; i < allWins.length; i++) {
|
|
try {
|
|
var win = allWins[i];
|
|
var proc = win.process();
|
|
if (proc && proc.name() !== "Finder" && proc.name() !== "Dock") {
|
|
var s = win.size();
|
|
if (s && s[0] > 10) {
|
|
win.position = [50 + stagger, 50 + stagger];
|
|
stagger += 30;
|
|
if (stagger > 150) stagger = 0;
|
|
}
|
|
}
|
|
} catch(e) {}
|
|
}
|
|
`;
|
|
try {
|
|
fs.writeFileSync(TEMP_JS_FILE, rescueScript, 'utf8');
|
|
execSync(`osascript -l JavaScript ${TEMP_JS_FILE} 2>/dev/null`);
|
|
} catch(e){}
|
|
if (fs.existsSync(TEMP_JS_FILE)) fs.unlinkSync(TEMP_JS_FILE);
|
|
console.log("Windows Cascaded to Laptop");
|
|
}
|
|
|
|
if (action === 'save') saveLayout();
|
|
if (action === 'restore') restoreLayout();
|
|
if (action === 'rescue') rescueWindows(); |