Initial commit of Hammerspoon config
This commit is contained in:
+163
@@ -0,0 +1,163 @@
|
||||
-- ~/.hammerspoon/NetworkMenu.lua
|
||||
NetworkMenu = {}
|
||||
NetworkMenu.menuBarItem = hs.menubar.new()
|
||||
|
||||
-- Global state management
|
||||
automationEnabled = (automationEnabled == nil) and true or automationEnabled
|
||||
local nasOnline = false
|
||||
local lastInternalIP = "Offline"
|
||||
local lastExternalIP = "Offline"
|
||||
local vpnActive = false
|
||||
|
||||
-- 1. ASYNC STATUS UPDATE (Prevents hangs and crashes)
|
||||
function updateStatus()
|
||||
if not config then
|
||||
print("NetworkMenu Error: Config not found")
|
||||
return
|
||||
end
|
||||
|
||||
-- A. Async NAS Ping
|
||||
hs.task.new("/sbin/ping", function(code)
|
||||
nasOnline = (code == 0)
|
||||
end, {"-c", "1", "-t", "1", config.nasIP}):start()
|
||||
|
||||
-- B. Internal VPN IP (Fast local command)
|
||||
local internalCmd = "ifconfig | grep -A 1 'utun' | grep 'inet ' | awk '{print $2}' | head -n 1"
|
||||
local internalIP = hs.execute(internalCmd):gsub("%s+", "")
|
||||
lastInternalIP = (internalIP == "" and "Offline" or internalIP)
|
||||
vpnActive = (internalIP ~= "")
|
||||
|
||||
-- C. Async External IP
|
||||
hs.task.new("/usr/bin/curl", function(exitCode, stdOut)
|
||||
if exitCode == 0 and stdOut then
|
||||
lastExternalIP = stdOut:gsub("%s+", "")
|
||||
else
|
||||
lastExternalIP = "Offline"
|
||||
end
|
||||
end, {"-s", "-m", "2", "icanhazip.com"}):start()
|
||||
|
||||
-- D. Update Menu Icon
|
||||
local currentSSID = hs.wifi.currentNetwork()
|
||||
if NetworkMenu.menuBarItem then
|
||||
local automationIcon = automationEnabled and "" or "🔘"
|
||||
local icon = (currentSSID == config.homeSSID) and "🏠" or "🌐"
|
||||
if vpnActive then icon = icon .. "🛡️" end
|
||||
if nasOnline then icon = icon .. "🟢" end
|
||||
NetworkMenu.menuBarItem:setTitle(automationIcon .. icon)
|
||||
end
|
||||
end
|
||||
|
||||
-- 2. Aggressive Backgrounding for OpenVPN
|
||||
function NetworkMenu.deepHideOpenVPN()
|
||||
local count = 0
|
||||
local hideTimer
|
||||
hideTimer = hs.timer.doEvery(0.1, function()
|
||||
local app = hs.application.get("OpenVPN Connect")
|
||||
if app then
|
||||
app:hide()
|
||||
hs.applescript.applescript('tell application "System Events" to set visible of process "OpenVPN Connect" to false')
|
||||
end
|
||||
count = count + 1
|
||||
if count > 30 then hideTimer:stop() end
|
||||
end)
|
||||
end
|
||||
|
||||
-- 3. Check Share Status (Local Filesystem Check)
|
||||
local function getShareStatus()
|
||||
local statusLines = {}
|
||||
if not config or not config.shares then return statusLines end
|
||||
for _, share in ipairs(config.shares) do
|
||||
local path = "/Volumes/" .. share
|
||||
local isMounted = hs.fs.attributes(path) ~= nil
|
||||
local icon = isMounted and "🟢" or "⚪️"
|
||||
table.insert(statusLines, { title = " " .. icon .. " " .. share, disabled = true })
|
||||
end
|
||||
return statusLines
|
||||
end
|
||||
|
||||
-- 4. THE MENU UI
|
||||
function NetworkMenu.buildMenu()
|
||||
local currentSSID = hs.wifi.currentNetwork() or "Disconnected"
|
||||
local isHome = config and (currentSSID == config.homeSSID)
|
||||
|
||||
local menu = {
|
||||
{ title = (automationEnabled and "🟢" or "🔴") .. " Automation: " .. (automationEnabled and "Enabled" or "Disabled"),
|
||||
fn = function()
|
||||
automationEnabled = not automationEnabled
|
||||
updateStatus()
|
||||
hs.alert.show("Network Automation: " .. (automationEnabled and "ON" or "OFF"))
|
||||
end
|
||||
},
|
||||
{ title = "-" },
|
||||
{ title = "📍 Profile: " .. (isHome and "Home" or "Remote"), disabled = true },
|
||||
{ title = " " .. (isHome and "🏠" or "🌐") .. " Location: " .. currentSSID, disabled = true },
|
||||
{ title = "-" },
|
||||
{ title = (vpnActive and "🔒" or "🔓") .. " VPN: " .. (vpnActive and "Connected" or "Disconnected"), disabled = true },
|
||||
{ title = " 🆔 Int IP: " .. lastInternalIP, disabled = true },
|
||||
{ title = " 🌍 Ext IP: " .. lastExternalIP, disabled = true },
|
||||
{ title = (nasOnline and "✅" or "❌") .. " NAS Status: " .. (nasOnline and "Online" or "Offline"), disabled = true },
|
||||
{ title = " 📂 Mounted Shares:", disabled = true },
|
||||
}
|
||||
|
||||
local shares = getShareStatus()
|
||||
for _, s in ipairs(shares) do table.insert(menu, s) end
|
||||
|
||||
table.insert(menu, { title = "-" })
|
||||
table.insert(menu, { title = "⚡️ VPN Controls:", disabled = true })
|
||||
table.insert(menu, { title = " 🔒 Connect VPN", fn = function()
|
||||
if config then
|
||||
hs.execute(string.format('"%s" --connect-shortcut=%s', config.openvpnPath, config.vpnProfileID))
|
||||
NetworkMenu.deepHideOpenVPN()
|
||||
hs.timer.doAfter(6, updateStatus)
|
||||
end
|
||||
end })
|
||||
|
||||
table.insert(menu, { title = " 🔓 Disconnect & Quit VPN", fn = function()
|
||||
if config then
|
||||
hs.execute(string.format('"%s" --disconnect-shortcut', config.openvpnPath))
|
||||
hs.timer.doAfter(1, function()
|
||||
local app = hs.application.get("OpenVPN Connect")
|
||||
if app then app:kill() end
|
||||
updateStatus()
|
||||
end)
|
||||
end
|
||||
end })
|
||||
|
||||
table.insert(menu, { title = "-" })
|
||||
table.insert(menu, { title = "📂 NAS Actions:", disabled = true })
|
||||
|
||||
table.insert(menu, { title = " ⬆️ Mount All Shares", fn = function()
|
||||
if config then
|
||||
for _, share in ipairs(config.shares) do
|
||||
hs.applescript.applescript(string.format('mount volume "smb://%s/%s"', config.nasIP, share))
|
||||
end
|
||||
end
|
||||
end })
|
||||
|
||||
table.insert(menu, { title = " ⬇️ Unmount All Shares", fn = function()
|
||||
if config then
|
||||
for _, share in ipairs(config.shares) do
|
||||
hs.execute(string.format("diskutil unmount /Volumes/%s", share))
|
||||
end
|
||||
end
|
||||
end })
|
||||
|
||||
table.insert(menu, { title = "-" })
|
||||
table.insert(menu, { title = "🔄 Refresh Status", fn = updateStatus })
|
||||
|
||||
return menu
|
||||
end
|
||||
|
||||
-- 5. INITIALIZATION
|
||||
if NetworkMenu.menuBarItem then
|
||||
NetworkMenu.menuBarItem:setMenu(NetworkMenu.buildMenu)
|
||||
-- Small delay before first status update to ensure config is ready
|
||||
hs.timer.doAfter(1, updateStatus)
|
||||
end
|
||||
|
||||
-- Watchers
|
||||
NetworkMenu.watcher = hs.wifi.watcher.new(updateStatus):start()
|
||||
NetworkMenu.timer = hs.timer.doEvery(30, updateStatus)
|
||||
|
||||
print("NetworkMenu Module Loaded Successfully")
|
||||
return NetworkMenu
|
||||
Reference in New Issue
Block a user