move nixos and darwin back into modules dir

This commit is contained in:
Noah Masur
2023-02-20 20:37:37 -05:00
parent ded498f4c9
commit cc84f1d37a
163 changed files with 30 additions and 30 deletions

View File

@ -0,0 +1 @@
indent_type = "Spaces"

View File

@ -0,0 +1,112 @@
--- === ControlEscape ===
---
--- Adapted very loosely from https://github.com/jasonrudolph/ControlEscape.spoon
--- Removed timing/delay; always send Escape as well as Control
---
--- Make the `control` key more useful: If the `control` key is tapped, treat it
--- as the `escape` key. If the `control` key is held down and used in
--- combination with another key, then provide the normal `control` key
--- behavior.
local obj = {}
obj.__index = obj
-- Metadata
obj.name = "ControlEscape"
obj.version = "0.1"
obj.author = "Jason Rudolph <jason@jasonrudolph.com>"
obj.homepage = "https://github.com/jasonrudolph/ControlEscape.spoon"
obj.license = "MIT - https://opensource.org/licenses/MIT"
function obj:init()
self.movements = 0
self.sendEscape = false
self.lastModifiers = {}
-- Create an eventtap to run each time the modifier keys change (i.e., each
-- time a key like control, shift, option, or command is pressed or released)
self.controlTap = hs.eventtap.new({ hs.eventtap.event.types.flagsChanged }, function(event)
local newModifiers = event:getFlags()
-- If this change to the modifier keys does not involve a *change* to the
-- up/down state of the `control` key (i.e., it was up before and it's
-- still up, or it was down before and it's still down), then don't take
-- any action.
if self.lastModifiers["ctrl"] == newModifiers["ctrl"] then
return false
end
-- Control was not down but is now
if not self.lastModifiers["ctrl"] then
-- Only prepare to send escape if no other modifier keys are in use
self.lastModifiers = newModifiers
if not self.lastModifiers["cmd"] and not self.lastModifiers["alt"] then
self.sendEscape = true
self.movements = 0
end
-- Control was down and is up, hasn't been blocked by another key, and
-- isn't above the movement threshold
elseif self.sendEscape == true and not newModifiers["ctrl"] and self.movements < 30 then
self.lastModifiers = newModifiers
-- Allow for shift-escape
if newModifiers["shift"] then
hs.eventtap.keyStroke({ "shift" }, "escape", 0)
else
hs.eventtap.keyStroke(newModifiers, "escape", 0)
end
self.sendEscape = false
self.movements = 0
self.numberOfCharacters = 0
-- Control was down and is up, but isn't ready to send escape
else
self.lastModifiers = newModifiers
end
end)
-- If any other key is pressed, don't send escape
self.asModifier = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function(_)
self.sendEscape = false
-- print("Don't sent escape")
end)
-- If mouse is moving significantly, don't send escape
self.scrolling = hs.eventtap.new({ hs.eventtap.event.types.gesture }, function(event)
local touches = event:getTouches()
local i, v = next(touches, nil)
while i do
if v["phase"] == "moved" then
-- Increment the movement counter
self.movements = self.movements + 1
end
i, v = next(touches, i) -- get next index
end
end)
end
--- ControlEscape:start()
--- Method
--- Start sending `escape` when `control` is pressed and released in isolation
function obj:start()
self.controlTap:start()
self.asModifier:start()
self.scrolling:start()
end
--- ControlEscape:stop()
--- Method
--- Stop sending `escape` when `control` is pressed and released in isolation
function obj:stop()
-- Stop monitoring keystrokes
self.controlTap:stop()
self.asModifier:stop()
self.scrolling:stop()
-- Reset state
self.sendEscape = false
self.lastModifiers = {}
end
return obj

View File

@ -0,0 +1,298 @@
/* Credit: https://gist.github.com/lancethomps/a5ac103f334b171f70ce2ff983220b4f */
function run(input, parameters) {
const appNames = [];
const skipAppNames = [];
const verbose = true;
const scriptName = "close_notifications_applescript";
const CLEAR_ALL_ACTION = "Clear All";
const CLEAR_ALL_ACTION_TOP = "Clear";
const CLOSE_ACTION = "Close";
const notNull = (val) => {
return val !== null && val !== undefined;
};
const isNull = (val) => {
return !notNull(val);
};
const notNullOrEmpty = (val) => {
return notNull(val) && val.length > 0;
};
const isNullOrEmpty = (val) => {
return !notNullOrEmpty(val);
};
const isError = (maybeErr) => {
return notNull(maybeErr) && (maybeErr instanceof Error || maybeErr.message);
};
const systemVersion = () => {
return Application("Finder").version().split(".").map(val => parseInt(val));
};
const systemVersionGreaterThanOrEqualTo = (vers) => {
return systemVersion()[0] >= vers;
};
const isBigSurOrGreater = () => {
return systemVersionGreaterThanOrEqualTo(11);
};
const V11_OR_GREATER = isBigSurOrGreater();
const APP_NAME_MATCHER_ROLE = V11_OR_GREATER ? "AXStaticText" : "AXImage";
const hasAppNames = notNullOrEmpty(appNames);
const hasSkipAppNames = notNullOrEmpty(skipAppNames);
const hasAppNameFilters = hasAppNames || hasSkipAppNames;
const appNameForLog = hasAppNames ? ` [${appNames.join(",")}]` : "";
const logs = [];
const log = (message, ...optionalParams) => {
let message_with_prefix = `${new Date().toISOString().replace("Z", "").replace("T", " ")} [${scriptName}]${appNameForLog} ${message}`;
console.log(message_with_prefix, optionalParams);
logs.push(message_with_prefix);
};
const logError = (message, ...optionalParams) => {
if (isError(message)) {
let err = message;
message = `${err}${err.stack ? (" " + err.stack) : ""}`;
}
log(`ERROR ${message}`, optionalParams);
};
const logErrorVerbose = (message, ...optionalParams) => {
if (verbose) {
logError(message, optionalParams);
}
};
const logVerbose = (message) => {
if (verbose) {
log(message);
}
};
const getLogLines = () => {
return logs.join("\n");
};
const getSystemEvents = () => {
let systemEvents = Application("System Events");
systemEvents.includeStandardAdditions = true;
return systemEvents;
};
const getNotificationCenter = () => {
try {
return getSystemEvents().processes.byName("NotificationCenter");
} catch (err) {
logError("Could not get NotificationCenter");
throw err;
}
};
const getNotificationCenterGroups = (retryOnError = false) => {
try {
let notificationCenter = getNotificationCenter();
if (notificationCenter.windows.length <= 0) {
return [];
}
if (!V11_OR_GREATER) {
return notificationCenter.windows();
}
return notificationCenter.windows[0].uiElements[0].uiElements[0].uiElements();
} catch (err) {
logError("Could not get NotificationCenter groups");
if (retryOnError) {
logError(err);
log("Retrying getNotificationCenterGroups...");
return getNotificationCenterGroups(false);
} else {
throw err;
}
}
};
const isClearButton = (description, name) => {
return description === "button" && name === CLEAR_ALL_ACTION_TOP;
};
const matchesAnyAppNames = (value, checkValues) => {
if (isNullOrEmpty(checkValues)) {
return false;
}
let lowerAppName = value.toLowerCase();
for (let checkValue of checkValues) {
if (lowerAppName === checkValue.toLowerCase()) {
return true;
}
}
return false;
};
const matchesAppName = (role, value) => {
if (role !== APP_NAME_MATCHER_ROLE) {
return false;
}
if (hasAppNames) {
return matchesAnyAppNames(value, appNames);
}
return !matchesAnyAppNames(value, skipAppNames);
};
const notificationGroupMatches = (group) => {
try {
let description = group.description();
if (V11_OR_GREATER && isClearButton(description, group.name())) {
return true;
}
if (V11_OR_GREATER && description !== "group") {
return false;
}
if (!V11_OR_GREATER) {
let matchedAppName = !hasAppNameFilters;
if (!matchedAppName) {
for (let elem of group.uiElements()) {
if (matchesAppName(elem.role(), elem.description())) {
matchedAppName = true;
break;
}
}
}
if (matchedAppName) {
return notNull(findCloseActionV10(group, -1));
}
return false;
}
if (!hasAppNameFilters) {
return true;
}
let firstElem = group.uiElements[0];
return matchesAppName(firstElem.role(), firstElem.value());
} catch (err) {
logErrorVerbose(`Caught error while checking window, window is probably closed: ${err}`);
logErrorVerbose(err);
}
return false;
};
const findCloseActionV10 = (group, closedCount) => {
try {
for (let elem of group.uiElements()) {
if (elem.role() === "AXButton" && elem.title() === CLOSE_ACTION) {
return elem.actions["AXPress"];
}
}
} catch (err) {
logErrorVerbose(`(group_${closedCount}) Caught error while searching for close action, window is probably closed: ${err}`);
logErrorVerbose(err);
return null;
}
log("No close action found for notification");
return null;
};
const findCloseAction = (group, closedCount) => {
try {
if (!V11_OR_GREATER) {
return findCloseActionV10(group, closedCount);
}
let checkForPress = isClearButton(group.description(), group.name());
let clearAllAction;
let closeAction;
for (let action of group.actions()) {
let description = action.description();
if (description === CLEAR_ALL_ACTION) {
clearAllAction = action;
break;
} else if (description === CLOSE_ACTION) {
closeAction = action;
} else if (checkForPress && description === "press") {
clearAllAction = action;
break;
}
}
if (notNull(clearAllAction)) {
return clearAllAction;
} else if (notNull(closeAction)) {
return closeAction;
}
} catch (err) {
logErrorVerbose(`(group_${closedCount}) Caught error while searching for close action, window is probably closed: ${err}`);
logErrorVerbose(err);
return null;
}
log("No close action found for notification");
return null;
};
const closeNextGroup = (groups, closedCount) => {
try {
for (let group of groups) {
if (notificationGroupMatches(group)) {
let closeAction = findCloseAction(group, closedCount);
if (notNull(closeAction)) {
try {
closeAction.perform();
return [true, 1];
} catch (err) {
logErrorVerbose(`(group_${closedCount}) Caught error while performing close action, window is probably closed: ${err}`);
logErrorVerbose(err);
}
}
return [true, 0];
}
}
return false;
} catch (err) {
logError("Could not run closeNextGroup");
throw err;
}
};
try {
let groupsCount = getNotificationCenterGroups(true).filter(group => notificationGroupMatches(group)).length;
if (groupsCount > 0) {
logVerbose(`Closing ${groupsCount}${appNameForLog} notification group${(groupsCount > 1 ? "s" : "")}`);
let startTime = new Date().getTime();
let closedCount = 0;
let maybeMore = true;
let maxAttempts = 2;
let attempts = 1;
while (maybeMore && ((new Date().getTime() - startTime) <= (1000 * 30))) {
try {
let closeResult = closeNextGroup(getNotificationCenterGroups(), closedCount);
maybeMore = closeResult[0];
if (maybeMore) {
closedCount = closedCount + closeResult[1];
}
} catch (innerErr) {
if (maybeMore && closedCount === 0 && attempts < maxAttempts) {
log(`Caught an error before anything closed, trying ${maxAttempts - attempts} more time(s).`)
attempts++;
} else {
throw innerErr;
}
}
}
} else {
throw Error(`No${appNameForLog} notifications found...`);
}
} catch (err) {
logError(err);
logError(err.message);
getLogLines();
throw err;
}
return getLogLines();
}

View File

@ -0,0 +1,17 @@
--- === Dismiss Alerts ===
local obj = {}
obj.__index = obj
-- Metadata
obj.name = "DismissAlerts"
obj.version = "0.1"
obj.license = "MIT - https://opensource.org/licenses/MIT"
function obj:init()
hs.hotkey.bind({ "cmd", "alt", "ctrl" }, "k", function()
hs.osascript.javascriptFromFile("Spoons/DismissAlerts.spoon/close_notifications_applescript.js")
end)
end
return obj

View File

@ -0,0 +1,107 @@
--- === Launcher ===
local obj = {}
obj.__index = obj
-- Metadata
obj.name = "Launcher"
obj.version = "0.1"
obj.license = "MIT - https://opensource.org/licenses/MIT"
function DrawSwitcher()
-- Drawing
local width = hs.screen.mainScreen():fullFrame().w
local switcherWidth = 500
local canv = hs.canvas.new({
x = width / 2 - switcherWidth / 2,
y = 1,
h = 3,
w = switcherWidth,
})
canv[#canv + 1] = {
action = "build",
type = "rectangle",
}
canv[#canv + 1] = {
type = "rectangle",
fillColor = { alpha = 1, red = 0.8, green = 0.6, blue = 0.3 },
action = "fill",
}
return canv:show()
end
function obj:init()
-- Begin launcher mode
if self.launcher == nil then
self.launcher = hs.hotkey.modal.new("ctrl", "space")
end
-- Behaviors on enter
function self.launcher:entered()
-- hs.alert("Entered mode")
self.canv = DrawSwitcher()
end
-- Behaviors on exit
function self.launcher:exited()
-- hs.alert("Exited mode")
self.canv:hide()
end
-- Use escape to exit launcher mode
self.launcher:bind("", "escape", function()
self.launcher:exit()
end)
-- Launcher shortcuts
self.launcher:bind("ctrl", "space", function() end)
self.launcher:bind("", "return", function()
self:switch("@kitty@")
end)
self.launcher:bind("", "C", function()
self:switch("Calendar.app")
end)
self.launcher:bind("", "D", function()
self:switch("@discord@")
end)
self.launcher:bind("", "E", function()
self:switch("Mail.app")
end)
self.launcher:bind("", "U", function()
self:switch("Music.app")
end)
self.launcher:bind("", "F", function()
self:switch("@firefox@")
end)
self.launcher:bind("", "H", function()
self:switch("Hammerspoon.app")
end)
self.launcher:bind("", "G", function()
self:switch("Mimestream.app")
end)
self.launcher:bind("", "M", function()
self:switch("Messages.app")
end)
self.launcher:bind("", "O", function()
self:switch("Obsidian.app")
end)
self.launcher:bind("", "P", function()
self:switch("System Preferences.app")
end)
self.launcher:bind("", "R", function()
hs.reload()
end)
self.launcher:bind("", "S", function()
self:switch("Slack.app")
end)
self.launcher:bind("", "Z", function()
self:switch("zoom.us.app")
end)
end
function obj:switch(app)
hs.application.launchOrFocus(app)
self.launcher:exit()
end
return obj

View File

@ -0,0 +1,52 @@
--- === Move Window ===
local obj = {}
obj.__index = obj
-- Metadata
obj.name = "MoveWindow"
obj.version = "0.1"
obj.license = "MIT - https://opensource.org/licenses/MIT"
function obj:init()
hs.window.animationDuration = 0
dofile(hs.spoons.resourcePath("worklayout.lua"))()
-- bind hotkey
hs.hotkey.bind({ "alt", "ctrl", "cmd" }, "n", function()
-- get the focused window
local win = hs.window.focusedWindow()
-- get the screen where the focused window is displayed, a.k.a. current screen
local screen = win:screen()
-- local nextScreen = screen:next()
-- compute the unitRect of the focused window relative to the current screen
-- and move the window to the next screen setting the same unitRect
win:moveToScreen(screen:next(), true, true, 0)
end)
hs.hotkey.bind({ "alt", "ctrl", "cmd" }, "b", function()
local win = hs.window.focusedWindow()
local screen = win:screen()
win:moveToScreen(screen:previous(), true, true, 0)
end)
hs.hotkey.bind({ "alt", "ctrl", "cmd" }, "m", function()
-- get the focused window
local win = hs.window.focusedWindow()
local frame = win:frame()
-- maximize if possible
local max = win:screen():fullFrame()
frame.x = max.x
frame.y = max.y
frame.w = max.w
frame.h = max.h
win:setFrame(frame)
-- -- first maximize to grid
-- hs.grid.maximizeWindow(win)
-- -- then spam maximize
-- for i = 1, 8 do
-- win:maximize()
-- end
end)
end
return obj

View File

@ -0,0 +1,60 @@
--- === Work Layout ===
-- Portions of this is adopted from:
-- https://github.com/anishathalye/dotfiles-local/tree/ffdadd313e58514eb622736b09b91a7d7eb7c6c9/hammerspoon
-- License is also available:
-- https://github.com/anishathalye/dotfiles-local/blob/ffdadd313e58514eb622736b09b91a7d7eb7c6c9/LICENSE.md
WORK_LEFT_MONITOR = "DELL U2415 (2)"
WORK_RIGHT_MONITOR = "DELL U2415 (1)"
LAPTOP_MONITOR = "Built-in Retina Display"
-- Used to find out the name of the monitor in Hammerspoon
-- local function dump(o)
-- if type(o) == "table" then
-- local s = "{ "
-- for k, v in pairs(o) do
-- if type(k) ~= "number" then
-- k = '"' .. k .. '"'
-- end
-- s = s .. "[" .. k .. "] = " .. dump(v) .. ","
-- end
-- return s .. "} "
-- else
-- return tostring(o)
-- end
-- end
-- Turn on when looking for the monitor name
-- print(dump(hs.screen.allScreens()))
local function concat(...)
local res = {}
for _, tab in ipairs({ ... }) do
for _, elem in ipairs(tab) do
table.insert(res, elem)
end
end
return res
end
local function worklayout()
hs.hotkey.bind({ "alt", "ctrl", "cmd" }, "l", function()
local u = hs.geometry.unitrect
-- set the layout
local left = {
{ "kitty", nil, WORK_LEFT_MONITOR, u(0, 0, 1, 1), nil, nil, visible = true },
}
local right = {
{ "Slack", nil, WORK_RIGHT_MONITOR, u(0, 0, 1, 1), nil, nil, visible = true },
{ "Mail", nil, WORK_RIGHT_MONITOR, u(0, 0, 1, 1), nil, nil, visible = true },
{ "zoom.us", nil, WORK_RIGHT_MONITOR, u(1 / 4, 1 / 4, 1 / 2, 1 / 2), nil, nil, visible = true },
}
local laptop = {
{ "Firefox", nil, LAPTOP_MONITOR, u(0, 0, 1, 1), nil, nil, visible = true },
}
local layout = concat(left, right, laptop)
hs.layout.apply(layout)
end)
end
return worklayout

View File

@ -0,0 +1,4 @@
hs.loadSpoon("ControlEscape"):start() -- Load Hammerspoon bits from https://github.com/jasonrudolph/ControlEscape.spoon
hs.loadSpoon("Launcher"):init()
hs.loadSpoon("DismissAlerts"):init()
hs.loadSpoon("MoveWindow"):init()