Last active
June 25, 2021 17:19
-
-
Save ttscoff/434fb51fb1098db966d8bf5720cbcfd2 to your computer and use it in GitHub Desktop.
Tools for BetterTouchTool script widgets (touch bar and menu bar)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# frozen_string_literal: true | |
# Memory and CPU stats formatted to use in a BetterTouchTool touch bar script widget | |
# Also works in menu bar (Other Triggers), but be sure to select "Use mono space font" | |
require 'optparse' | |
# Settings | |
settings = { | |
chart_width: 8, # character width of chart, > 2 | |
# Colors for level indicator, can be hex or rgb(a) | |
# max is cutoff percentage for indicator level | |
colors: [ | |
{ | |
max: 60, color: 'rgba(162, 191, 138, 1.00)' | |
}, | |
{ | |
max: 75, color: 'rgba(236, 204, 135, 1.00)' | |
}, | |
{ | |
max: 90, color: 'rgba(210, 135, 109, 1.00)' | |
}, | |
{ | |
max: 1000, color: 'rgba(197, 85, 98, 1.00)' | |
} | |
] | |
} | |
# defaults | |
options = { | |
percent: false, | |
mem_free: false, | |
averages: [1, 5, 15], | |
color_indicator: 1, | |
top: false, | |
background: false, | |
raw: false, | |
truncate: 0, | |
spacer: nil, | |
title: '' | |
} | |
parser = OptionParser.new do |opts| | |
opts.banner = "Usage: #{File.basename(__FILE__)} [options] [subcommand]" | |
opts.separator 'Subcommands:' | |
opts.separator ' cpu (default)' | |
opts.separator ' memory' | |
opts.separator ' ip [lan|wan (default)]' | |
opts.separator ' network [interface|location (default)]' | |
opts.separator ' doing' | |
opts.separator '' | |
opts.separator 'Options:' | |
opts.on('-a', '--averages AVERAGES', | |
'Comma separated list of CPU averages to display (1, 5, 15), default all') do |c| | |
options[:averages] = c.split(/,/).map(&:to_i) | |
end | |
opts.on('-c', '--color_from AVERAGE', 'Which CPU average to get level indicator color from (1, 5, 15)') do |c| | |
options[:color_indicator] = c.to_i | |
end | |
opts.on('-f', '--free', 'Display free memory instead of used memory') do | |
options[:mem_free] = true | |
end | |
opts.on('-i', '--indicator', 'Include background color indicating severity') do | |
options[:background] = true | |
end | |
opts.on('-p', '--percent', 'Display percentages instead of bar graph') do | |
options[:percent] = true | |
end | |
opts.on('-t', '--title TITLE', 'Include title in output, [TITLE][CHART/%]') do |c| | |
options[:title] = c | |
end | |
opts.on('--truncate LENGTH', 'Truncate output') do |c| | |
options[:truncate] = c.to_i | |
end | |
opts.on('--top', 'If CPU is maxed, include top process in output') do | |
options[:top] = true | |
end | |
opts.on('--no-btt', 'Output raw text without BetterTouchTool formatting') do | |
options[:raw] = true | |
end | |
opts.on('--empty CHARACTER', 'Include character when output is empty to prevent widget from collapsing') do |c| | |
options[:spacer] = c | |
end | |
opts.on('-h', '--help', 'Display this screen') do | |
puts opts | |
exit | |
end | |
end | |
parser.parse! | |
def rgba_to_btt(color) | |
elements = color.match(/rgba?\((?<r>\d+), *(?<g>\d+), *(?<b>\d+)(?:, *(?<a>[\d.]+))?\)/) | |
alpha = elements['a'] ? 255 * elements['a'].to_f : 255 | |
%(#{elements['r']},#{elements['g']},#{elements['b']},#{alpha.to_i}) | |
end | |
def hex_to_btt(color) | |
rgb = color.match(/^#?(..)(..)(..)$/).captures.map(&:hex) | |
"#{rgb.join(',')},255" | |
end | |
def color_to_btt(color) | |
color = color.strip.gsub(/ +/, '').downcase | |
case color | |
when /^rgba?\(\d+,\d+,\d+(,(0?\.\d+|1(\.0+)?))?\)$/ | |
rgba_to_btt(color) | |
when /^#?[a-f0-9]{6}$/ | |
hex_to_btt(color) | |
else | |
'25,25,25,255' | |
end | |
end | |
case ARGV[0] | |
when /^net/ | |
options[:background] = false | |
chart = case ARGV[1] | |
when /^i/ # Interface | |
`osascript -e 'tell app "System Events" to tell current location of network preferences to get name of first service whose active is true'`.strip | |
else # Location | |
`osascript -e 'tell app "System Events" to get name of current location of network preferences'`.strip | |
end | |
when /^ip/ | |
options[:background] = false | |
chart = case ARGV[1] | |
when /^lan/ # LAN IP | |
`ifconfig | grep "inet " | awk '{ print $2 }' | tail -n 1`.strip | |
else # WAN IP | |
`curl -SsL icanhazip.com`.strip | |
end | |
when /^doing/ | |
## Add in .doingrc (`doing config`) | |
# views: | |
# btt: | |
# section: Currently | |
# count: 1 | |
# order: desc | |
# template: "%title" | |
# tags_bool: NONE | |
# tags: done | |
options[:background] = false | |
chart = `/usr/local/bin/doing view btt`.strip | |
when /^m/ | |
mem_free = `memory_pressure | tail -n 1 | awk '{ print $NF; }' | tr -d '%'`.to_i | |
mem_used = 100 - mem_free | |
memory = options[:mem_free] ? mem_free : mem_used | |
if options[:percent] | |
chart = "#{memory}%" | |
else | |
unit = (settings[:chart_width].to_f / 100) | |
chart_arr = Array.new(settings[:chart_width], '░') | |
chart_arr.fill('█', 0, (unit * memory).to_i) | |
chart = chart_arr.join('') | |
end | |
color = '' | |
settings[:colors].each do |c| | |
if mem_used <= c[:max] | |
color = color_to_btt(c[:color]) | |
break | |
end | |
end | |
else | |
cores = `sysctl -n hw.ncpu`.to_i | |
all_loads = `sysctl -n vm.loadavg`.split(/ /)[1..3] | |
loads = [] | |
loads.push(all_loads[0]) if options[:averages].include?(1) | |
loads.push(all_loads[1]) if options[:averages].include?(5) | |
loads.push(all_loads[2]) if options[:averages].include?(15) | |
indicator = case options[:color_indicator] | |
when 5 | |
all_loads[1] | |
when 15 | |
all_loads[2] | |
else | |
all_loads[0] | |
end | |
curr_load = ((indicator.to_f / cores) * 100).to_i | |
curr_load = 100 if curr_load > 100 | |
if options[:percent] | |
chart = loads.map { |ld| "#{((ld.to_f / cores) * 100).to_i}%" }.join('|') | |
else | |
unit = (settings[:chart_width].to_f / 100) | |
chart_arr = Array.new(settings[:chart_width], '░') | |
fills = %w[▒ ▓ █].reverse.slice(0, loads.length).reverse | |
loads.reverse.each_with_index do |ld, idx| | |
this_load = ((ld.to_f / cores) * 100) | |
this_load = 100 if this_load > 100 | |
chart_arr.fill(fills[idx], 0, (unit * this_load).to_i) | |
end | |
chart = chart_arr.join('') | |
end | |
color = '' | |
if options[:background] | |
settings[:colors].each do |c| | |
if curr_load <= c[:max] | |
color = color_to_btt(c[:color]) | |
break | |
end | |
end | |
end | |
if options[:top] && curr_load >= 100 | |
top_process = `ps -arcwwwxo "command"|iconv -c -f utf-8 -t ascii|head -n 2|tail -n 1`.strip | |
chart += " (#{top_process})" | |
end | |
end | |
chart = chart[0..options[:truncate]] if options[:truncate].positive? && chart.length > options[:truncate] | |
chart = options[:spacer] if chart.empty? && options[:spacer] | |
chart = "#{options[:title]}#{chart}" | |
if options[:raw] | |
print chart | |
else | |
out = %({\"text\": \"#{chart}\") | |
out += options[:background] ? %(,\"background_color\":\"#{color}\"}) : '}' | |
print out | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment