diff --git a/modules/bridge.lua b/modules/bridge.lua new file mode 100644 index 0000000..ccca2e4 --- /dev/null +++ b/modules/bridge.lua @@ -0,0 +1,10 @@ +local focus = require("modules.focus") + +-- Expose clean functions globally for the hs CLI tool +function ActivateDeepFocus(profileName) + return focus.activate(profileName) +end + +function ClearDeepFocus() + return focus.clear() +end \ No newline at end of file diff --git a/modules/focus.lua b/modules/focus.lua new file mode 100644 index 0000000..c841e04 --- /dev/null +++ b/modules/focus.lua @@ -0,0 +1,88 @@ +local M = {} + +local focusCanvas = nil +local activeProfile = nil +local configPath = hs.configdir .. "/focus_profiles.json" + +local function loadProfiles() + local file = io.open(configPath, "r") + if not file then + print("Error: Could not open focus_profiles.json") + return nil + end + local content = file:read("*a") + file:close() + return hs.json.decode(content) +end + +local function manageApps(profileConfig) + for _, appName in ipairs(profileConfig.close) do + local app = hs.application.get(appName) + if app then app:kill() end + end + + for _, appName in ipairs(profileConfig.keepOpen) do + hs.application.launchOrFocus(appName) + end +end + +local function setFocusOverlay(alpha) + if focusCanvas then + focusCanvas:delete() + focusCanvas = nil + end + + if not alpha or alpha == 0 then return end + + local mainScreen = hs.screen.mainScreen() + local rect = mainScreen:frame() + + focusCanvas = hs.canvas.new(rect) + focusCanvas:insertElement({ + action = "fill", + type = "rectangle", + fillColor = { red = 0, green = 0, blue = 0, alpha = alpha } + }) + + focusCanvas:level(50) + focusCanvas:show() + + local frontApp = hs.application.frontmostApplication() + if frontApp then + local win = frontApp:mainWindow() + if win then win:focus() end + end +end + +function M.activate(profileName) + local profiles = loadProfiles() + if not profiles or not profiles[profileName] then + print("Error: Profile '" .. tostring(profileName) .. "' not found.") + return "Profile not found" + end + + activeProfile = profileName + local currentProfileConfig = profiles[profileName] + + manageApps(currentProfileConfig) + setFocusOverlay(currentProfileConfig.dimAlpha) + + return "Activated " .. profileName .. " mode" +end + +function M.clear() + setFocusOverlay(nil) + activeProfile = nil + return "Focus mode cleared" +end + +hs.screen.watcher.new(function() + if activeProfile then + local profiles = loadProfiles() + if profiles and profiles[activeProfile] then + setFocusOverlay(profiles[activeProfile].dimAlpha) + end + end +end):start() + +return M \ No newline at end of file diff --git a/modules/spoon_config.lua b/modules/spoon_config.lua new file mode 100644 index 0000000..674cf15 --- /dev/null +++ b/modules/spoon_config.lua @@ -0,0 +1,60 @@ +-- ======================================================================== +-- SPOON RUNTIME CONFIGURATION MODULE +-- ======================================================================== + +---- SpeedMenu Implementation +if spoon.SpeedMenu then + -- Define the Fix Function (includes MAC and IPv6 parsing via shell) + local function applyFullMenuFix() + local interface = spoon.SpeedMenu.interface or "en0" + local ssid = hs.wifi.currentNetwork() or "Disconnected" + local details = hs.network.interfaceDetails(interface) + + local ipv4 = (details and details.IPv4) and details.IPv4.Addresses[1] or "N/A" + local ipv6 = (details and details.IPv6) and details.IPv6.Addresses[1] or "N/A" + + -- Get MAC Address via native shell pipe + local macaddr = hs.execute('ifconfig ' .. interface .. ' | grep ether | awk \'{print $2}\''):gsub("%s+", "") + + local menuitems = { + { title = "SSID: " .. ssid, fn = function() hs.pasteboard.setContents(ssid) end }, + { title = "IPv4: " .. ipv4, fn = function() hs.pasteboard.setContents(ipv4) end }, + { title = "IPv6: " .. ipv6, fn = function() hs.pasteboard.setContents(ipv6) end }, + { title = "MAC: " .. macaddr, fn = function() hs.pasteboard.setContents(macaddr) end }, + { title = "-" }, + { title = "Rescan Network Interfaces", fn = function() spoon.SpeedMenu:rescan() end } + } + spoon.SpeedMenu.menubar:setMenu(menuitems) + end + + -- Overwrite the baseline rescan hook cleanly + local oldRescan = spoon.SpeedMenu.rescan + spoon.SpeedMenu.rescan = function(self) + oldRescan(self) + applyFullMenuFix() + end + + -- Toggle Engine State Manager + local speedMenuRunning = false + hs.hotkey.bind({"cmd", "alt", "ctrl"}, "W", function() + if speedMenuRunning then + spoon.SpeedMenu:stop() + hs.alert.show("SpeedMenu Stopped") + else + spoon.SpeedMenu:start() + applyFullMenuFix() + hs.alert.show("SpeedMenu Started") + end + speedMenuRunning = not speedMenuRunning + end) +end + +---- BrewInfo Implementation +if spoon.BrewInfo then + hs.hotkey.bind({"cmd", "alt", "ctrl"}, "I", function() + spoon.BrewInfo:showBrewInfo() + end) + hs.hotkey.bind({"cmd", "alt", "ctrl"}, "J", function() + spoon.BrewInfo:showBrewInfoCurSel() + end) +end \ No newline at end of file