diff --git a/_Spoons/CPUMEMBAT.spoon/init.lua b/_Spoons/CPUMEMBAT.spoon/init.lua new file mode 100644 index 0000000..6c58cc8 --- /dev/null +++ b/_Spoons/CPUMEMBAT.spoon/init.lua @@ -0,0 +1,257 @@ +-- TODO: +-- * need a better name +-- * make more spoon like (hotkeys, metadata, etc.) +-- * honor changes to checkInterval without having to stop first and then show +-- * document +-- * make appropriate for use outside of panel (behavior, level, etc) +-- * check memory usage; can we differentiate between truly in use vs disk caching to get a better idea of true "Free" memory? + +local canvas = require("hs.canvas") +local battery = require("hs.battery") +local timer = require("hs.timer") +local stext = require("hs.styledtext") +local host = require("hs.host") +local obj = {} + +local newFullBarGuage = function(barWidth) + barWidth = barWidth or obj.barWidth + return stext.new({ string.rep("|", barWidth), + { + starts = 1, + ends = barWidth / 2, + attributes = { + font = obj.baseFont, + color = { list = "ansiTerminalColors", name = "fgGreen" }, + } + }, { + starts = barWidth / 2 + 1, + ends = 3 * barWidth / 4, + attributes = { + font = obj.baseFont, + color = { list = "ansiTerminalColors", name = "fgYellow" }, + } + }, { + starts = 3 * barWidth / 4 + 1, + ends = barWidth, + attributes = { + font = obj.baseFont, + color = { list = "ansiTerminalColors", name = "fgRed" }, + } + } + }) +end + +obj.updateDisplay = function(cpuUsage) + obj._cpuPoll = nil + +-- CPU Usage + + local cpuGuage = newFullBarGuage() + local cpuActive = math.floor(cpuUsage.overall.active + .5) + if cpuActive < 100 then + cpuGuage = cpuGuage:setStyle({ color = obj.emptyBar }, math.ceil(obj.barWidth * cpuActive / 100), #cpuGuage) + end + + cpuGuage = stext.new("CPU ", { + font = obj.baseFont, + color = { + list = "ansiTerminalColors", + name = "fgBlack" + }, + }) .. cpuGuage .. stext.new(string.format(" %3d%% utilization", cpuActive), { + font = obj.baseFont, + color = { + list = "ansiTerminalColors", + name = (cpuActive > 75) and "fgRed" or ((cpuActive > 50) and "fgYellow" or "fgGreen"), + }, + }) + +-- RAM Usage + + local ramGuage = newFullBarGuage() + + -- from top-107 source at http://opensource.apple.com/release/os-x-10116/ + local vm = host.vmStat() + local totalFree = (vm.pagesFree + vm.pagesSpeculative) * vm.pageSize + local totalUsed = (vm.pagesWiredDown + vm.pagesInactive + vm.pagesActive + vm.pagesUsedByVMCompressor) * vm.pageSize + totalFree = totalFree / (1024 * 1024 * 1024) -- convert to GB + totalUsed = totalUsed / (1024 * 1024 * 1024) -- convert to GB + + local totalRam = totalFree + totalUsed + local percentInUse = totalUsed / totalRam + + ramGuage = ramGuage:setStyle({ color = obj.emptyBar }, math.ceil(obj.barWidth * percentInUse), #ramGuage) + + ramGuage = stext.new("RAM ", { + font = obj.baseFont, + color = { + list = "ansiTerminalColors", + name = "fgBlack" + }, + }) .. ramGuage + + local active = math.floor(100 * percentInUse) + ramGuage = ramGuage .. stext.new(string.format(" %3d%% used, %.2fGB free", active, totalFree), { + font = obj.baseFont, + color = { + list = "ansiTerminalColors", + name = (active > 75) and "fgRed" or ((active > 50) and "fgYellow" or "fgGreen"), + }, + }) + +-- Battery Usage + + local batteryGuage = newFullBarGuage() + + local max_charge = battery.maxCapacity() + local cur_charge = battery.capacity() + + percentInUse = max_charge and cur_charge and (cur_charge / max_charge) or 0 + batteryGuage = batteryGuage:setStyle({ color = obj.emptyBar }, 1, math.ceil(obj.barWidth * (1 - percentInUse))) + + batteryGuage = stext.new("Bat ", { + font = obj.baseFont, + color = { + list = "ansiTerminalColors", + name = "fgBlack" + }, + }) .. batteryGuage + + if max_charge and cur_charge then + local avail = math.floor(100 * percentInUse) + batteryGuage = batteryGuage .. stext.new(string.format(" %3d%% charged, %d mAh", avail, cur_charge), { + font = obj.baseFont, + color = { + list = "ansiTerminalColors", + name = (avail > 50) and "fgGreen" or ((avail > 25) and "fgYellow" or "fgRed"), + }, + }) + else + batteryGuage = batteryGuage .. stext.new(" n/a", { + font = stext.convertFont(obj.baseFont, stext.fontTraits.italicFont), + color = { + list = "ansiTerminalColors", + name = "fgBlack" + }, + }) + end + +-- Build Output + + -- styledtext concatenation isn't honoring the style settings for the text *after* the line break... until I can + -- determine if this is a bug or a limitation, this workaround does the trick + local lineBreak = stext.new("\n", { font = obj.baseFont }) + + local final = cpuGuage .. lineBreak .. ramGuage .. lineBreak .. batteryGuage + + if obj.includeTime then + final = final .. lineBreak .. stext.new("Last check: " .. os.date("%c"), { + font = stext.convertFont({ + name = obj.baseFont.name, + size = obj.baseFont.size - 2, + }, stext.fontTraits.italicFont), + color = { + list = "ansiTerminalColors", + name = "fgBlack" + }, + paragraphStyle = { alignment = "right" }, + }) + end + + local outputSize = obj.canvas:minimumTextSize(final) + + obj.canvas.output.text = final + obj.canvas.output.frame = { + x = obj.padding, + y = obj.padding, + h = outputSize.h, + w = outputSize.w, + } + obj.canvas:frame{ + x = obj.location.x, + y = obj.location.y, + h = outputSize.h + obj.padding * 2, + w = outputSize.w + obj.padding * 2, + } + + -- in case they changed + obj.canvas.background.roundedRectRadii = { xRadius = obj.cornerRadius, yRadius = obj.cornerRadius } + obj.canvas.background.fillColor = obj.backgroundColor + obj.canvas.background.strokeColor = obj.backgroundBorder + + -- override default repeat interval with the actual desired value because it may change between calls + -- wrapped in if just in case :hide is called after triggered but before HS actually runs this callback + -- (unlikely, but I've seen weirder) + if obj._timer then obj._timer:setNextTrigger(obj.checkInterval) end +end + +obj.baseFont = { name = "Menlo", size = 12 } +obj.barWidth = 20 +obj.emptyBar = { white = 0.5 } +obj.padding = 10 +obj.location = { x = 100, y = 100 } +obj.checkInterval = 30 +obj.timeSlice = 1 +obj.includeTime = true +obj.cornerRadius = 5 +obj.backgroundColor = { alpha = .7, white = .5 } +obj.backgroundBorder = { alpha = .5 } + +-- a typical height and width for this output on my machine; it will change as soon as there is data, so accuracy isn't important +local defaultSize = { h = 88, w = 360 } +obj.canvas = canvas.new{ x = obj.location.x, y = obj.location.y, h = defaultSize.h, w = defaultSize.w, } + +local initialMsg = stext.new("awaiting data collection", { + font = stext.convertFont(obj.baseFont, stext.fontTraits.italicFont), + color = { + list = "ansiTerminalColors", + name = "fgBlack" + }, + paragraphStyle = { alignment = "center" }, +}) +local msgSize = obj.canvas:minimumTextSize(initialMsg) + +obj.canvas[#obj.canvas + 1] = { + id = "background", + type = "rectangle", + fillColor = obj.backgroundColor, + strokeColor = obj.backgroundBorder, + roundedRectRadii = { xRadius = obj.cornerRadius, yRadius = obj.cornerRadius }, + clipToPath = true, -- makes for sharper edges +} +obj.canvas[#obj.canvas + 1] = { + id = "output", + type = "text", + text = initialMsg, + frame = { + x = (defaultSize.w - msgSize.w) / 2, + y = (defaultSize.h - msgSize.h) / 2, + h = msgSize.h, + w = msgSize.w, + }, +} + +obj.show = function(self) + self = self or obj -- correct for calling this as a function + if not obj._timer then + -- we use setNextTrigger to start and maintain the timer, so the actual interval here is irrelevant + obj._timer = timer.doEvery(300, function() + obj._cpuPoll = host.cpuUsage(obj.timeSlice, obj.updateDisplay) + end) + end + obj.canvas:show() + obj._timer:setNextTrigger(0) + return self +end + +obj.hide = function(self) + self = self or obj -- correct for calling this as a function + if obj._timer then + obj._timer:stop() + obj._timer = nil + end + obj.canvas:hide() + return self +end + +return obj diff --git a/geekery.lua b/geekery.lua index a21c54d..b0d541f 100644 --- a/geekery.lua +++ b/geekery.lua @@ -8,14 +8,14 @@ local monitorTopY = screen.mainScreen():frame().y local monitorBotY = monitorTopY + screen.mainScreen():frame().h --geekery.registerShellGeeklet("cpu", 15, "geeklets/system.sh", -geekery.registerLuaGeeklet("cpu", 5, "geeklets/system.lua", - { x = 22, y = monitorTopY + 22, h = 60, w = 350}, { color = { alpha = 1 }, skip = true }, - { drawing.rectangle{ x = 12, y = monitorTopY + 12, h = 80, w = 370 } - :setFillColor{ alpha=.7, white = .5 } - :setStrokeColor{ alpha=.5 } - :setFill(true) - :setRoundedRectRadii(5,5) - }):start() +-- geekery.registerLuaGeeklet("cpu", 5, "geeklets/system.lua", +-- { x = 22, y = monitorTopY + 22, h = 60, w = 350}, { color = { alpha = 1 }, skip = true }, +-- { drawing.rectangle{ x = 12, y = monitorTopY + 12, h = 80, w = 370 } +-- :setFillColor{ alpha=.7, white = .5 } +-- :setStrokeColor{ alpha=.5 } +-- :setFill(true) +-- :setRoundedRectRadii(5,5) +-- }):start() geekery.registerShellGeeklet("wifi", 60, "geeklets/wifi.sh", { x = 22, y = monitorTopY + 102, h = 60, w = 350}, { @@ -63,36 +63,36 @@ geekery.registerLuaGeeklet("remoteCheck", 300, geekletRemoteCheck, -- :setRoundedRectRadii(5,5) -- }):start() -local geekletClock = function() - local self = geekery.geeklets.clock - local screenFrame = screen.mainScreen():fullFrame() - local clockTime = os.date("%I:%M:%S %p") - local clockPos = drawing.getTextDrawingSize(clockTime, self.textStyle) - clockPos.w = clockPos.w + 4 - clockPos.x = screenFrame.x + screenFrame.w - (clockPos.w + 4) - clockPos.y = screenFrame.y + screenFrame.h - (clockPos.h + 4) - local clockBlockPos = { - x = clockPos.x - 3, - y = clockPos.y, - h = clockPos.h + 3, - w = clockPos.w + 6, - } - self.drawings[2]:setFrame(clockBlockPos) - self.drawings[1]:setFrame(clockPos) - return clockTime -end - -geekery.registerLuaGeeklet("clock", 1, geekletClock, { }, { - font = { name = "Menlo-Italic", size = 12, }, - color = { red=.75, blue=.75, green=.75, alpha=.75}, - paragraphStyle = { alignment = "center", lineBreak = "clip" } - }, { - drawing.rectangle{}:setStroke(true) - :setStrokeColor({ red=.75, blue=.75, green=.75, alpha=.75}) - :setFill(true) - :setFillColor({alpha=.75}) - :setRoundedRectRadii(5,5) - }):hover(true):start() -geekery.geeklets.clock.hoverlock = true +-- local geekletClock = function() +-- local self = geekery.geeklets.clock +-- local screenFrame = screen.mainScreen():fullFrame() +-- local clockTime = os.date("%I:%M:%S %p") +-- local clockPos = drawing.getTextDrawingSize(clockTime, self.textStyle) +-- clockPos.w = clockPos.w + 4 +-- clockPos.x = screenFrame.x + screenFrame.w - (clockPos.w + 4) +-- clockPos.y = screenFrame.y + screenFrame.h - (clockPos.h + 4) +-- local clockBlockPos = { +-- x = clockPos.x - 3, +-- y = clockPos.y, +-- h = clockPos.h + 3, +-- w = clockPos.w + 6, +-- } +-- self.drawings[2]:setFrame(clockBlockPos) +-- self.drawings[1]:setFrame(clockPos) +-- return clockTime +-- end +-- +-- geekery.registerLuaGeeklet("clock", 1, geekletClock, { }, { +-- font = { name = "Menlo-Italic", size = 12, }, +-- color = { red=.75, blue=.75, green=.75, alpha=.75}, +-- paragraphStyle = { alignment = "center", lineBreak = "clip" } +-- }, { +-- drawing.rectangle{}:setStroke(true) +-- :setStrokeColor({ red=.75, blue=.75, green=.75, alpha=.75}) +-- :setFill(true) +-- :setFillColor({alpha=.75}) +-- :setRoundedRectRadii(5,5) +-- }):hover(true):start() +-- geekery.geeklets.clock.hoverlock = true geekery.startUpdates() diff --git a/geeklets/system.lua b/geeklets/system.lua deleted file mode 100644 index b858311..0000000 --- a/geeklets/system.lua +++ /dev/null @@ -1,157 +0,0 @@ -local battery = require("hs.battery") -local styledtext = require("hs.styledtext") -local host = require("hs.host") - -local baseFont = { name = "Menlo", size = 12 } -local barWidth = 20 -local emptyBar = { white = 0.5 } - --- CPU Usage - -local cpuGuage = styledtext.new({ - string.rep("|", barWidth), { - starts = 1, - ends = barWidth / 2, - attributes = { - font = baseFont, - color = { list = "ansiTerminalColors", name = "fgGreen" }, - } - }, { - starts = barWidth / 2 + 1, - ends = 3 * barWidth / 4, - attributes = { - font = baseFont, - color = { list = "ansiTerminalColors", name = "fgYellow" }, - } - }, { - starts = 3 * barWidth / 4 + 1, - ends = barWidth, - attributes = { - font = baseFont, - color = { list = "ansiTerminalColors", name = "fgRed" }, - } - } -}) - -local cpuActive = host.cpuUsage().overall.active - -local avail = barWidth * cpuActive / 100 -cpuGuage = cpuGuage:setStyle({ color = emptyBar }, math.ceil(avail), #cpuGuage) - -cpuGuage = styledtext.new("CPU ", { - font = baseFont, - color = { list = "ansiTerminalColors", name = "fgBlack" }, -}) .. cpuGuage - -avail = math.floor(cpuActive) -local tailColor = { list = "ansiTerminalColors", name = "fgRed" } -if avail < 50 then - tailColor = { list = "ansiTerminalColors", name = "fgGreen" } -elseif avail < 75 then - tailColor = { list = "ansiTerminalColors", name = "fgYellow" } -end -cpuGuage = cpuGuage .. styledtext.new(" " .. tostring(avail) .. "% utilization\n", { font = baseFont, color = tailColor }) - --- RAM Usage - -local ramGuage = styledtext.new({ - string.rep("|", barWidth), { - starts = 1, - ends = barWidth / 2, - attributes = { - font = baseFont, - color = { list = "ansiTerminalColors", name = "fgGreen" }, - } - }, { - starts = barWidth / 2 + 1, - ends = 3 * barWidth / 4, - attributes = { - font = baseFont, - color = { list = "ansiTerminalColors", name = "fgYellow" }, - } - }, { - starts = 3 * barWidth / 4 + 1, - ends = barWidth, - attributes = { - font = baseFont, - color = { list = "ansiTerminalColors", name = "fgRed" }, - } - } -}) - --- from top-107 source at http://opensource.apple.com/release/os-x-10116/ -local vm = host.vmStat() -local totalFree = (vm.pagesFree + vm.pagesSpeculative) * vm.pageSize -local totalUsed = (vm.pagesWiredDown + vm.pagesInactive + vm.pagesActive + vm.pagesUsedByVMCompressor) * vm.pageSize - -totalFree = totalFree / (1024 * 1024 * 1024) -- convert to GB -totalUsed = totalUsed / (1024 * 1024 * 1024) -- convert to GB -local totalRam = totalFree + totalUsed - -local avail = barWidth * totalUsed / totalRam -ramGuage = ramGuage:setStyle({ color = emptyBar }, math.ceil(avail), #ramGuage) - -ramGuage = styledtext.new("RAM ", { - font = baseFont, - color = { list = "ansiTerminalColors", name = "fgBlack" }, -}) .. ramGuage - -avail = math.floor(100 * totalUsed / totalRam) -local tailColor = { list = "ansiTerminalColors", name = "fgRed" } -if avail < 50 then - tailColor = { list = "ansiTerminalColors", name = "fgGreen" } -elseif avail < 75 then - tailColor = { list = "ansiTerminalColors", name = "fgYellow" } -end -ramGuage = ramGuage .. styledtext.new(" " .. tostring(avail) .. "% Used, " .. string.format("%.2fGB Free\n", totalFree), { font = baseFont, color = tailColor }) - --- Battery Usage - -local batteryGuage = styledtext.new({ - string.rep("|", barWidth), { - starts = 1, - ends = barWidth / 2, - attributes = { - font = baseFont, - color = { list = "ansiTerminalColors", name = "fgGreen" }, - } - }, { - starts = barWidth / 2 + 1, - ends = 3 * barWidth / 4, - attributes = { - font = baseFont, - color = { list = "ansiTerminalColors", name = "fgYellow" }, - } - }, { - starts = 3 * barWidth / 4 + 1, - ends = barWidth, - attributes = { - font = baseFont, - color = { list = "ansiTerminalColors", name = "fgRed" }, - } - } -}) - -local max_charge = battery.maxCapacity() -local cur_charge = battery.capacity() - -local avail = barWidth * cur_charge / max_charge -batteryGuage = batteryGuage:setStyle({ color = emptyBar }, 1, math.ceil(barWidth - avail)) - -batteryGuage = styledtext.new("Bat ", { - font = baseFont, - color = { list = "ansiTerminalColors", name = "fgBlack" }, -}) .. batteryGuage - -avail = math.floor(100 * cur_charge / max_charge) -local tailColor = { list = "ansiTerminalColors", name = "fgGreen" } -if avail < 25 then - tailColor = { list = "ansiTerminalColors", name = "fgRed" } -elseif avail < 50 then - tailColor = { list = "ansiTerminalColors", name = "fgYellow" } -end -batteryGuage = batteryGuage .. styledtext.new(" " .. tostring(avail) .. "% charged, " .. tostring(cur_charge) .. "(mAh) Remain\n", { font = baseFont, color = tailColor }) - - -- remove trailing \n -- I may add to this or rearrange them, so this lets my cut/pasting - -- be lazy -return (cpuGuage .. ramGuage .. batteryGuage):sub(1, -2) diff --git a/utils/_panels/infoPanel.lua b/utils/_panels/infoPanel.lua index a76762f..77342e2 100644 --- a/utils/_panels/infoPanel.lua +++ b/utils/_panels/infoPanel.lua @@ -1,6 +1,7 @@ local slidingPanels = hs.loadSpoon("SlidingPanels") slidingPanels:addPanel("infoPanel", { + side = "top", size = 1/3, modifiers = { "fn" }, persistent = true, @@ -21,7 +22,7 @@ slidingPanels:addPanel("infoPanel", { slidingPanels:panel("infoPanel"):addWidget("FromSpoon", "HCalendar", { rX = "100%", bY = "100%" }) -slidingPanels:panel("infoPanel"):addWidget("FromSpoon", "CircleClock", { rX = "100%", y = 0 }, { +slidingPanels:panel("infoPanel"):addWidget("FromSpoon", "CircleClock", { rX = "100%", y = 0 }, { background = { type = "rectangle", action = "fill", @@ -30,9 +31,21 @@ slidingPanels:panel("infoPanel"):addWidget("FromSpoon", "CircleClock", { rX = }, }) -slidingPanels:panel("infoPanel"):addWidget("FromSpoon", "MountedVolumes", { x = 0, bY = "100%" }, { - start = "show", -- could also be `function(spoon) spoon:show() end`` - vars = { cornerRadius = 20 }, +slidingPanels:panel("infoPanel"):addWidget("FromSpoon", "MountedVolumes", { x = 0, bY = "100%" }, { + start = function(spoon) + spoon.textStyle.font.size = 9 -- easier then spelling out entire style in vars + -- need to think about separating out font from style in spoon + spoon:show() + end, + vars = { cornerRadius = 20, }, +}) + +slidingPanels:panel("infoPanel"):addWidget("FromSpoon", "CPUMEMBAT", { x = 0, y = 0 }, { + start = "show", -- could also be `function(spoon) spoon:show() end` + vars = { + checkInterval = 10, + baseFont = { name = "Menlo", size = 10 }, + }, }) return slidingPanels:panel("infoPanel")