diff --git a/xbar/brew-services.10m.rb b/xbar/brew-services.10m.rb index a202096..24f0c40 100755 --- a/xbar/brew-services.10m.rb +++ b/xbar/brew-services.10m.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true # Brew Services -# v3.1.2 +# v3.2.0 # Jim Myhrberg # jimeh # List and manage Homebrew Services @@ -23,15 +23,60 @@ # rubocop:disable Metrics/PerceivedComplexity # rubocop:disable Style/IfUnlessModifier +require 'fileutils' require 'open3' require 'json' require 'set' +# Xbar is a tiny helper library for creating Xbar and SwiftBar plugins in Ruby. module Xbar class CommandError < StandardError; end class RPCError < StandardError; end + module Helpers + def plugin_data_path + @plugin_data_path ||= (swiftbar_data_path || File.dirname(__FILE__)) + end + + def plugin_file_path + @plugin_file_path ||= (swiftbar_plugin_path || __FILE__) + end + + def plugin_filename + @plugin_filename ||= File.basename(plugin_file_path) + end + + def plugin_name + @plugin_name ||= begin + parts = plugin_filename.split('.') + if parts.size < 3 + raise "Invalid plugin name: #{plugin_filename}" + end + + parts[0..-3].join('.') + end + end + + def swiftbar? + ENV['SWIFTBAR'] == '1' + end + + def swiftbar_cache_path + @swiftbar_cache_path ||= ENV['SWIFTBAR_PLUGIN_CACHE_PATH'] if swiftbar? + end + + def swiftbar_data_path + @swiftbar_data_path ||= ENV['SWIFTBAR_PLUGIN_DATA_PATH'] if swiftbar? + end + + def swiftbar_plugin_path + @swiftbar_plugin_path ||= ENV['SWIFTBAR_PLUGIN_PATH'] if swiftbar? + end + end + module Service + include Helpers + private def config @@ -42,8 +87,11 @@ module Xbar @printer ||= ::Xbar::Printer.new end - def cmd(*args) - out, err, s = Open3.capture3(*args) + def cmd(*args, dir: nil) + opts = {} + opts[:chdir] = File.expand_path(dir) if dir + + out, err, s = Open3.capture3(*args, opts) if s.exitstatus != 0 msg = "Command failed: #{args.join(' ')}" msg += ": #{err}" unless err.empty? @@ -72,7 +120,16 @@ module Xbar end end + # Config is a simple wrapper around a JSON file that contains the plugin's + # configuration. It is compatible with Xbar's behavior of loading environment + # variables from it. We don't rely on that however and directly read the file + # ourselves. + # + # In SwiftBar, we use `SWIFTBAR_PLUGIN_DATA_PATH` to determine where to store + # the configuration in a `config.json` file. class Config < Hash + include Helpers + def initialize super @@ -88,15 +145,25 @@ module Xbar end def filename - @filename ||= "#{__FILE__}.vars.json" + @filename ||= File.join( + plugin_data_path, + swiftbar? ? 'config.json' : "#{plugin_filename}.vars.json" + ) + end + + def dirname + @dirname ||= File.dirname(filename) end def save + FileUtils.mkdir_p(dirname) File.write(filename, JSON.pretty_generate(self)) end end class Printer + include Helpers + attr_reader :nested_level SUB_STR = '--' @@ -140,13 +207,25 @@ module Xbar end def plugin_refresh_uri - @plugin_refresh_uri ||= 'xbar://app.xbarapp.com/refreshPlugin' \ - "?path=#{File.basename(__FILE__)}" + return @plugin_refresh_uri if @plugin_refresh_uri + + if swiftbar? + @plugin_refresh_uri = "swiftbar://refreshplugin?name=#{plugin_name}" + else + @plugin_refresh_uri = 'xbar://app.xbarapp.com/refreshPlugin' \ + "?path=#{plugin_filename}" + end end def normalize_props(props = {}) props = props.dup + # Explicitly set terminal to false for SwiftBar, as it seems to default + # to true when not specified. At least with SwiftBar 2.0.1. + if swiftbar? && !props.key?(:terminal) + props[:terminal] = false + end + if props[:rpc] && props[:shell].nil? props[:shell] = [__FILE__] + props[:rpc] props.delete(:rpc) @@ -160,8 +239,12 @@ module Xbar end end - # Refresh Xbar after shell command has run in terminal - if props[:terminal] && props[:refresh] && props[:shell] + # Always refresh SwiftBar via refresh plugin URI so as to temporarily + # disable the menu bar icon while refresh is running. + # + # For Xbar this does not work when terminal is false, so we only trigger + # refresh via the plugin refresh URI when terminal is true. + if props[:refresh] && props[:shell] && (swiftbar? || props[:terminal]) props[:refresh] = false i = 1 i += 1 while props.key?("param#{i}".to_sym)