barabara 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/bin/barabara +11 -0
- data/lib/barabara.rb +44 -0
- data/lib/barabara/app.rb +46 -0
- data/lib/barabara/config.rb +108 -0
- data/lib/barabara/modules/battery.rb +105 -0
- data/lib/barabara/modules/clock.rb +38 -0
- data/lib/barabara/modules/event_processor.rb +30 -0
- data/lib/barabara/modules/lemonbar.rb +80 -0
- data/lib/barabara/modules/volume.rb +100 -0
- data/lib/barabara/modules/weather.rb +72 -0
- data/lib/barabara/modules/wm.rb +136 -0
- data/lib/barabara/modules/wname.rb +26 -0
- data/lib/barabara/modules/wttr.rb +57 -0
- data/lib/barabara/version.rb +3 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5fc704ae054747cbedd0ddd81d1306b8fbb0f430839f80aaafda64cdf29225cd
|
4
|
+
data.tar.gz: 4357e8b018f5b770808f31b35747e30586289295e2b7834b39438608631a6103
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1ea2f0049b07c412363316266425db72818b56e5fbd3aa122a1006eae248ecbbaf51432964a2c8bc2920117900d610d3d92c1525137906ee1417ce7d591bad25
|
7
|
+
data.tar.gz: 5159919bf03b2314c5f8ae990a177dcd55b3ff84667de3db2a4446c6d2cbfb30a077fdd16088398bbb0c8818c043877086eb399b8fe7e3e5e429d87a1004032d
|
data/Gemfile
ADDED
data/bin/barabara
ADDED
data/lib/barabara.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'yaml'
|
6
|
+
require 'wisper'
|
7
|
+
require 'barabara/config'
|
8
|
+
require 'barabara/app'
|
9
|
+
|
10
|
+
module Barabara
|
11
|
+
DEFAULT_CONF = '~/.config/barabara.conf.yml'
|
12
|
+
@init = true
|
13
|
+
|
14
|
+
def self.run_app
|
15
|
+
require 'optimist'
|
16
|
+
opts = Optimist::options do
|
17
|
+
opt :config, "Path to config file",
|
18
|
+
type: :string, default: DEFAULT_CONF
|
19
|
+
end
|
20
|
+
|
21
|
+
config_path = check_config(opts[:config])
|
22
|
+
@app = App.new(config_path)
|
23
|
+
@app.run
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def self.check_config(path)
|
29
|
+
config_path = File.expand_path path
|
30
|
+
if ! File.exists? config_path
|
31
|
+
if path == DEFAULT_CONF
|
32
|
+
warn 'Config file not found at default location!'
|
33
|
+
warn 'Will write a new one right now...'
|
34
|
+
# TODO dump default config to a file.
|
35
|
+
Configuration.dump_default_config(config_path)
|
36
|
+
else
|
37
|
+
warn "Config file \"#{path}\" not found!"
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
config_path
|
43
|
+
end
|
44
|
+
end
|
data/lib/barabara/app.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Dir[File.join(__dir__, 'modules/*.rb')].each { |lib| require lib }
|
4
|
+
|
5
|
+
module Barabara
|
6
|
+
class App
|
7
|
+
attr_accessor :threads
|
8
|
+
def initialize(config_path)
|
9
|
+
@threads = []
|
10
|
+
@config = GlobalConfig.init(config_path)
|
11
|
+
bootstrap
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
fill_threads
|
16
|
+
@threads.each(&:join)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def bootstrap
|
22
|
+
@modules = @config.modules
|
23
|
+
session = @config.session
|
24
|
+
@modules << Modules::WindowName if session == 'bspwm'
|
25
|
+
end
|
26
|
+
|
27
|
+
def fill_threads
|
28
|
+
@threads << Thread.new do
|
29
|
+
Thread.current.name = 'Event Parser'
|
30
|
+
Wisper.subscribe(Modules::EventProcessor.new, on: :event)
|
31
|
+
end
|
32
|
+
|
33
|
+
@threads << Thread.new do
|
34
|
+
Thread.current.name = 'Panel Feed'
|
35
|
+
Wisper.subscribe(Modules::Lemonbar.new, on: :update_panel)
|
36
|
+
end
|
37
|
+
|
38
|
+
@modules.each do |mod|
|
39
|
+
@threads << Thread.new do
|
40
|
+
Thread.current.name = mod.to_s
|
41
|
+
mod.new.watch
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# This file contains the definition of Configuration class,
|
3
|
+
# which will store global Barabara configuration and make it
|
4
|
+
# available to every entity as a singleton object.
|
5
|
+
|
6
|
+
require 'barabara/modules/wm'
|
7
|
+
|
8
|
+
module Barabara
|
9
|
+
# Load configuration from a file and store it as an object.
|
10
|
+
class Configuration
|
11
|
+
attr_reader :config, :modules, :session, :monitors
|
12
|
+
|
13
|
+
def initialize(config_file)
|
14
|
+
@config = YAML.load_file config_file
|
15
|
+
@modules = parse_module_list(@config['modules'])
|
16
|
+
@session = ENV['XDG_SESSION_DESKTOP']
|
17
|
+
@monitors = Modules::WM.get_monitors(@session)
|
18
|
+
end
|
19
|
+
|
20
|
+
def colors; @config['colors']; end
|
21
|
+
def module_options; @config['module_options']; end
|
22
|
+
|
23
|
+
def module_config(mod_name)
|
24
|
+
return {} unless module_options.key?(mod_name)
|
25
|
+
|
26
|
+
module_options[mod_name]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.dump_default_config(path)
|
30
|
+
File.open(File.expand_path(path), 'w') do |f|
|
31
|
+
f.write default_config.to_yaml
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def parse_module_list(list)
|
38
|
+
list.map do |mod|
|
39
|
+
begin
|
40
|
+
Object.const_get("Barabara::Modules::#{mod}")
|
41
|
+
rescue NameError
|
42
|
+
warn "Module \"#{mod}\" not found!"
|
43
|
+
next
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def dump(default: false)
|
49
|
+
(default ? default_config : config).to_yaml
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.default_config
|
53
|
+
{
|
54
|
+
"modules" => ["Battery", "WM", "Clock", "Wttr", "Volume"],
|
55
|
+
"colors" => { al_winbi: "#000000", in_framebr: "#101010",
|
56
|
+
in_framebg: "#565656", in_winbr: "#454545",
|
57
|
+
ac_framebr: "#222222", ac_framebg: "#345F0C",
|
58
|
+
ac_winbr: "#9FBC00", ac_winbo: "#3E4A00",
|
59
|
+
ac_winbi: "#3E4A00", ur_winbr: "#FF0675",
|
60
|
+
se_text: "#101010", in_text: "#909090",
|
61
|
+
mi_text: "#BCBCBC", ac_text: "#EFEFEF" },
|
62
|
+
"module_options" => {
|
63
|
+
"lemonbar" => {
|
64
|
+
name: "barabara", height: 12,
|
65
|
+
format: "%%{S%<monitor>s}%%{l}%<tags>s%<sep>s %%{c} %<window_title>s %%{r} %<volume>s %<battery>s %<sep>s %<time>s %<sep>s %<weather>s\n",
|
66
|
+
fonts: {
|
67
|
+
text: "-lucy-tewi-medium-*-normal-*-11-*-*-*-*-*-*-*",
|
68
|
+
glyphs: "-wuncon-siji-medium-r-normal-*-10-100-75-75-c-80-iso10646-1"
|
69
|
+
},
|
70
|
+
snippets: { sep: "%%{B-}%%{F%<ac_winbr>s}|%%{F-}" },
|
71
|
+
extra_opts: ["| sh"]
|
72
|
+
},
|
73
|
+
"clock" => {
|
74
|
+
"format" => "%%H%%{F%<in_text>s}:%%{F-}%%M %%{F%<in_text>s}%%Y%%{F%<mi_text>s}%%m%%{F-}%%d"
|
75
|
+
},
|
76
|
+
"wm" => {
|
77
|
+
"tag_icons" => {
|
78
|
+
"mail" => "", "work" => "", "web" => "",
|
79
|
+
"im" => "", "term" => "", "dev" => "",
|
80
|
+
"files" => "", "doc" => "", "docs" => "",
|
81
|
+
"misc" => ""
|
82
|
+
}
|
83
|
+
},
|
84
|
+
"battery" => { "icons" => { 'low' => "", 'med' => "", 'high' => "", 'full' => "", 'charge' => "" } },
|
85
|
+
"volume" => { "icons" => { 'mute' => "", 'low' => "", 'med' => "", 'max' => "" } },
|
86
|
+
"weather" => {
|
87
|
+
"api_key" => "<YOUR API KEY HERE>", "location" => "London", "unit" => "c",
|
88
|
+
"format" => "%%{F%<ac_winbr>s}%<icon>s%%{F-} %<temp>s°"
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Make configuration accessible as a Singleton object.
|
96
|
+
class GlobalConfig
|
97
|
+
class << self
|
98
|
+
def init(path)
|
99
|
+
@path = File.expand_path(path)
|
100
|
+
@config = Configuration.new(@path)
|
101
|
+
end
|
102
|
+
|
103
|
+
def config
|
104
|
+
@config ||= Configuration.new(@path)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This module provides battery status functionality.
|
4
|
+
# At the moment it just reads a status file from kernel's sysfs,
|
5
|
+
# so it only supports Linux.
|
6
|
+
#
|
7
|
+
# TODO Add Upower support and make it possible
|
8
|
+
# to select the preferred mode from config.
|
9
|
+
module Barabara
|
10
|
+
module Modules
|
11
|
+
class Battery
|
12
|
+
# Needed for message bus support
|
13
|
+
include Wisper::Publisher
|
14
|
+
|
15
|
+
# Predefined status icons:
|
16
|
+
ICONS = {
|
17
|
+
'low' => "\ue034",
|
18
|
+
'med' => "\ue036",
|
19
|
+
'high' => "\ue037",
|
20
|
+
'full' => "\ue09e",
|
21
|
+
'charge' => "\ue041"
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
# Initialize new Battery object.
|
25
|
+
#
|
26
|
+
# @param name [String] System battery name.
|
27
|
+
def initialize(config = GlobalConfig.config.module_config('battery'))
|
28
|
+
@name = config['name'] || ENV['BAT']
|
29
|
+
@path = "/sys/class/power_supply/#{@name}/uevent"
|
30
|
+
@capacity = 100
|
31
|
+
@power = 0.0
|
32
|
+
@status = 'U'
|
33
|
+
@icons = config['icons'] || ICONS
|
34
|
+
end
|
35
|
+
|
36
|
+
# Read battery status from sysfs.
|
37
|
+
# Only updates the object attributes, does not return anything.
|
38
|
+
def parse!
|
39
|
+
IO.readlines(@path).each do |line|
|
40
|
+
case line
|
41
|
+
when /POWER_SUPPLY_STATUS=/
|
42
|
+
@status = line.split('=')[1][0]
|
43
|
+
when /POWER_SUPPLY_CAPACITY=/
|
44
|
+
@capacity = line.split('=')[1].to_i
|
45
|
+
when /POWER_SUPPLY_POWER_NOW=/
|
46
|
+
@power = line.split('=')[1].to_f / 10**6
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Select battery status icon.
|
52
|
+
#
|
53
|
+
# @return [String] Battery status icon.
|
54
|
+
def icon
|
55
|
+
return 'U' unless @status == 'D'
|
56
|
+
|
57
|
+
case @capacity
|
58
|
+
when 0..35 then @icons['low']
|
59
|
+
when 36..65 then @icons['med']
|
60
|
+
when 66..100 then @icons['high']
|
61
|
+
else 'U'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Prepare output format string.
|
66
|
+
#
|
67
|
+
# @return [String] Format string suitable for Kernel#printf.
|
68
|
+
def format_string
|
69
|
+
case @status
|
70
|
+
when 'F' then @icons['full']
|
71
|
+
when 'C' then @icons['charge'] + ' %<capacity>d%%'
|
72
|
+
else
|
73
|
+
if @power > 3.5
|
74
|
+
'%<icon>s %<capacity>d%%:%<power>.0fW'
|
75
|
+
else
|
76
|
+
'%<icon>s %<capacity>d%%:%<power>.1fW'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Convert battery attributes to hash.
|
82
|
+
#
|
83
|
+
# @return [Hash] Attribute hash suitable for String#format.
|
84
|
+
def to_h
|
85
|
+
{ icon: icon, capacity: @capacity, power: @power }
|
86
|
+
end
|
87
|
+
|
88
|
+
# Render battery status as a string.
|
89
|
+
#
|
90
|
+
# @return [String] Battery status (ready for sending to the panel).
|
91
|
+
def render
|
92
|
+
parse!
|
93
|
+
format(format_string, to_h)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Enter event loop and feed the message bus with events.
|
97
|
+
def watch
|
98
|
+
loop do
|
99
|
+
publish(:event, 'battery', render)
|
100
|
+
sleep @status == 'C' ? 30 : 10
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Barabara
|
2
|
+
module Modules
|
3
|
+
class Clock
|
4
|
+
include Wisper::Publisher
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
config = GlobalConfig.config.module_config('clock')
|
8
|
+
colors = GlobalConfig.config.colors
|
9
|
+
@format = format(config['format'], colors) || '%F %R'
|
10
|
+
@time = Time.now
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :time
|
14
|
+
|
15
|
+
def watch
|
16
|
+
loop do
|
17
|
+
update
|
18
|
+
push
|
19
|
+
sleep 5
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def push
|
26
|
+
publish(:event, 'time', render)
|
27
|
+
end
|
28
|
+
|
29
|
+
def update
|
30
|
+
@time = Time.now
|
31
|
+
end
|
32
|
+
|
33
|
+
def render
|
34
|
+
@time.strftime(@format)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Barabara
|
3
|
+
module Modules
|
4
|
+
class EventProcessor
|
5
|
+
include Wisper::Publisher
|
6
|
+
|
7
|
+
def event(command, args)
|
8
|
+
return false if command == ''
|
9
|
+
|
10
|
+
# STDERR.puts "Got command \"#{command}\": #{args.inspect}."
|
11
|
+
out = case command
|
12
|
+
when 'tagline', 'battery', 'weather', 'time', 'volume'
|
13
|
+
{ command.to_sym => args }
|
14
|
+
when /^(focus|window_title)_changed$/
|
15
|
+
{ window_title: Modules::WindowName.limit(args[1] || '') }
|
16
|
+
when 'window_title'
|
17
|
+
{ window_title: sanitize_window_title(args || '') }
|
18
|
+
else
|
19
|
+
warn "Unknown event \"#{command}\": " + args.inspect
|
20
|
+
{}
|
21
|
+
end
|
22
|
+
publish(:update_panel, out)
|
23
|
+
end
|
24
|
+
|
25
|
+
def sanitize_window_title(title)
|
26
|
+
title.gsub('%{', '%%{')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'pty'
|
2
|
+
|
3
|
+
module Barabara
|
4
|
+
module Modules
|
5
|
+
class Lemonbar
|
6
|
+
attr_reader :panel_pid, :panel_clicks
|
7
|
+
attr_accessor :panel_out, :panel_data
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
options = GlobalConfig.config.module_config('lemonbar')
|
11
|
+
@colors = GlobalConfig.config.colors
|
12
|
+
@monitors = GlobalConfig.config.monitors
|
13
|
+
@format = options[:format].chomp
|
14
|
+
@snippets = fill_snippets(options[:snippets])
|
15
|
+
@cmd = ['lemonbar', *bar_options(options)].join(' ')
|
16
|
+
@panel_data = bootstrap_panel
|
17
|
+
|
18
|
+
run_panel
|
19
|
+
end
|
20
|
+
|
21
|
+
def fill_snippets(snippets)
|
22
|
+
Hash[snippets.map { |k, v| [k, v % @colors] }]
|
23
|
+
end
|
24
|
+
|
25
|
+
def run_panel
|
26
|
+
@panel_clicks, slave = PTY.open
|
27
|
+
read, @panel_out = IO.pipe
|
28
|
+
@panel_pid = spawn(@cmd, in: read, out: slave)
|
29
|
+
slave.close
|
30
|
+
read.close
|
31
|
+
end
|
32
|
+
|
33
|
+
def bar_options(options)
|
34
|
+
cmd_opts = [
|
35
|
+
"-B '#{@colors[:in_framebr]}'",
|
36
|
+
"-F '#{@colors[:ac_text]}'",
|
37
|
+
"-g x#{options[:height]}+0+0",
|
38
|
+
"-n #{options[:name]}", '-a 30',
|
39
|
+
"-f #{options[:fonts][:text]}",
|
40
|
+
"-f #{options[:fonts][:glyphs]}"
|
41
|
+
]
|
42
|
+
cmd_opts << options[:extra_opts] if options.key?(:extra_opts)
|
43
|
+
cmd_opts
|
44
|
+
end
|
45
|
+
|
46
|
+
def bootstrap_panel
|
47
|
+
@snippets.merge(
|
48
|
+
window_title: 'Welcome home.',
|
49
|
+
tagline: {},
|
50
|
+
battery: 'U',
|
51
|
+
weather: '',
|
52
|
+
time: '',
|
53
|
+
volume: Volume.new.update
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
def fill_panel
|
58
|
+
string = ''
|
59
|
+
# STDERR.puts 'Panel data:' + @panel_data.inspect
|
60
|
+
@monitors.each do |monitor|
|
61
|
+
string << format(@format,
|
62
|
+
@panel_data.merge(
|
63
|
+
tags: @panel_data.dig(:tagline, monitor) || '',
|
64
|
+
monitor: monitor
|
65
|
+
))
|
66
|
+
end
|
67
|
+
string
|
68
|
+
end
|
69
|
+
|
70
|
+
def render
|
71
|
+
@panel_out.puts fill_panel + "\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
def update_panel(data)
|
75
|
+
@panel_data.merge!(data)
|
76
|
+
render
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Barabara
|
4
|
+
module Modules
|
5
|
+
class Volume
|
6
|
+
include Wisper::Publisher
|
7
|
+
# Class constants (Icons) here.
|
8
|
+
ICONS = {
|
9
|
+
'mute' => "\ue04f",
|
10
|
+
'low' => "\ue04e",
|
11
|
+
'med' => "\ue050",
|
12
|
+
'max' => "\ue05d"
|
13
|
+
}.freeze
|
14
|
+
CMD_WATCH = 'pactl subscribe'.freeze
|
15
|
+
CMD_SET = 'amixer -q set Master'.freeze
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
options = GlobalConfig.config.module_config('volume')
|
19
|
+
@colors = GlobalConfig.config.colors
|
20
|
+
@icons = options['icons'] || ICONS
|
21
|
+
|
22
|
+
@icon = 'mute'
|
23
|
+
@color = :ac_text
|
24
|
+
@mute = true
|
25
|
+
@level = 0
|
26
|
+
fetch
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse
|
30
|
+
return [:in_text, 'mute'] if @mute
|
31
|
+
|
32
|
+
case @level
|
33
|
+
when 60..100 then [:ac_text, 'max']
|
34
|
+
when 30..60 then [:mi_text, 'med']
|
35
|
+
when 0..30 then [:in_text, 'low']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def watch
|
40
|
+
PTY.spawn(CMD_WATCH) do |read, _write, pid|
|
41
|
+
read.each { |line| parse_line(line.chomp) }
|
42
|
+
Process.wait pid
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_line(line)
|
47
|
+
publish(:event, 'volume', update) if line.match?(/^Event 'change' on sink/)
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch
|
51
|
+
sleep 0.01
|
52
|
+
raw_data = `amixer get Master`
|
53
|
+
keys = raw_data
|
54
|
+
.match(/Front Left:.* \[(?<level>\d+)%\] \[(?<state>[onf]+)\]/)
|
55
|
+
.named_captures
|
56
|
+
|
57
|
+
@level = keys['level'].to_i
|
58
|
+
@mute = keys['state'] == 'off'
|
59
|
+
@color, @icon = parse
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def mute
|
64
|
+
spawn(CMD_SET + ' toggle')
|
65
|
+
fetch
|
66
|
+
end
|
67
|
+
|
68
|
+
def up
|
69
|
+
spawn(CMD_SET + ' 2%+ unmute')
|
70
|
+
fetch
|
71
|
+
end
|
72
|
+
|
73
|
+
def down
|
74
|
+
spawn(CMD_SET + ' 2%- unmute')
|
75
|
+
fetch
|
76
|
+
end
|
77
|
+
|
78
|
+
def format_string
|
79
|
+
if @mute
|
80
|
+
'%%{F%<color>s}%<icon>s%%{F-}'
|
81
|
+
else
|
82
|
+
'%%{F%<color>s}%<icon>s %<level>s%%%%%%{F-}'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_h
|
87
|
+
{ icon: @icons[@icon], color: @colors[@color], level: @level }
|
88
|
+
end
|
89
|
+
|
90
|
+
def render
|
91
|
+
format(format_string, to_h)
|
92
|
+
end
|
93
|
+
|
94
|
+
def update
|
95
|
+
fetch
|
96
|
+
render
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Barabara
|
5
|
+
module Modules
|
6
|
+
class Weather
|
7
|
+
include Wisper::Publisher
|
8
|
+
require 'net/http'
|
9
|
+
require 'json'
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
options = GlobalConfig.config.module_config('weather')
|
13
|
+
@colors = GlobalConfig.config.colors
|
14
|
+
|
15
|
+
@api_key = options['api_key'] || '0'
|
16
|
+
@location = options['location'] || 'London'
|
17
|
+
@uri = format_uri(@api_key, @location)
|
18
|
+
@raw_data = fetch
|
19
|
+
@temp = 0
|
20
|
+
@unit = options['unit'] || 'c'
|
21
|
+
@icon = '?'
|
22
|
+
@format = options['format']
|
23
|
+
end
|
24
|
+
|
25
|
+
def format_uri(api_key, location)
|
26
|
+
uri = URI('http://api.apixu.com/v1/current.json')
|
27
|
+
query = URI.encode_www_form(key: api_key, q: location)
|
28
|
+
uri.query = query
|
29
|
+
uri
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch
|
33
|
+
Net::HTTP.get_response(@uri)
|
34
|
+
rescue SocketError
|
35
|
+
'⌚'
|
36
|
+
end
|
37
|
+
|
38
|
+
def cond_icon(condition)
|
39
|
+
case condition
|
40
|
+
when /cloudy|cast|fog|mist/i then '☁'
|
41
|
+
when /clear|sunny/i then '☀'
|
42
|
+
when /outbreaks|rain|drizzle|thunder/i then '☂'
|
43
|
+
when /sleet|ice|snow/i then '☃'
|
44
|
+
else '?'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse!
|
49
|
+
weatherdata = JSON.parse(@raw_data.body)['current']
|
50
|
+
@temp, condition = weatherdata.fetch_values("temp_#{@unit}", 'condition')
|
51
|
+
@icon = cond_icon(condition['text'])
|
52
|
+
end
|
53
|
+
|
54
|
+
def render
|
55
|
+
@raw_data = fetch
|
56
|
+
return '⌚' unless @raw_data.is_a?(Net::HTTPSuccess)
|
57
|
+
|
58
|
+
parse!
|
59
|
+
sign = '+' if @temp.positive?
|
60
|
+
format(@format,
|
61
|
+
{ temp: "#{sign}#{@temp.to_i}", icon: @icon }.merge(@colors))
|
62
|
+
end
|
63
|
+
|
64
|
+
def watch
|
65
|
+
loop do
|
66
|
+
publish(:event, 'weather', render)
|
67
|
+
sleep 900
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'pty'
|
2
|
+
|
3
|
+
module Barabara
|
4
|
+
module Modules
|
5
|
+
class WM
|
6
|
+
include Wisper::Publisher
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@tag_icons = GlobalConfig.config.module_config('wm')['tag_icons'] || {}
|
10
|
+
@colors = GlobalConfig.config.colors
|
11
|
+
@monitors = GlobalConfig.config.monitors
|
12
|
+
@cmd, @wm = detect_wm(GlobalConfig.config.session)
|
13
|
+
end
|
14
|
+
|
15
|
+
def detect_wm(wmname)
|
16
|
+
case wmname
|
17
|
+
when 'herbstluftwm'
|
18
|
+
['herbstclient --idle', 'hlwm']
|
19
|
+
when 'bspwm'
|
20
|
+
['bspc subscribe report', 'bspwm']
|
21
|
+
else
|
22
|
+
raise NameError, 'Unknown WM!'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :cmd, :wm
|
27
|
+
|
28
|
+
def parse_line(line)
|
29
|
+
case @wm
|
30
|
+
when 'hlwm'
|
31
|
+
command, *args = line.split("\t")
|
32
|
+
if command == 'tag_changed'
|
33
|
+
publish(:event, 'tagline', hlwm_tagline)
|
34
|
+
else
|
35
|
+
publish(:event, command, args)
|
36
|
+
end
|
37
|
+
when 'bspwm'
|
38
|
+
# We remove first char here, because it's insignificant in our case.
|
39
|
+
tags = line[1..-1].split(':')
|
40
|
+
publish(:event, 'tagline', parse_bspwm_tags(tags))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def watch
|
45
|
+
if @wm == 'hlwm'
|
46
|
+
publish(:event, 'tagline', hlwm_tagline)
|
47
|
+
else
|
48
|
+
parse_line(`bspc wm -g`.chomp)
|
49
|
+
end
|
50
|
+
PTY.spawn(@cmd) do |read, _write, pid|
|
51
|
+
read.each { |line| parse_line(line.chomp) }
|
52
|
+
Process.wait pid
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def tag_color(status)
|
57
|
+
## Tag statuses:
|
58
|
+
# '#' -- Tag is active and focused on current monitor;
|
59
|
+
# '+' -- Tag is active on current monitor,
|
60
|
+
# but another monitor is focused;
|
61
|
+
# ':' -- Tag is not active, but contains windows;
|
62
|
+
# '!' -- Tag contains an urgent window.
|
63
|
+
case status
|
64
|
+
when /[#OF]/ then { bg: @colors[:ac_winbr], fg: @colors[:se_text] }
|
65
|
+
when /[+M]/ then { bg: '#9CA668', fg: @colors[:se_text] }
|
66
|
+
when /[:o]/ then { bg: @colors[:in_framebr], fg: @colors[:ac_text] }
|
67
|
+
when /[!uU]/ then { bg: @colors[:ur_winbr], fg: @colors[:se_text] }
|
68
|
+
when /[-%m]/ then { bg: @colors[:in_text], fg: @colors[:in_framebr] }
|
69
|
+
else { bg: @colors[:in_framebr], fg: @colors[:in_text] }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def hlwm_tagline
|
74
|
+
tagline = {}
|
75
|
+
@monitors.each do |monitor|
|
76
|
+
# Switch the font to glyphs:
|
77
|
+
tagline[monitor] = '%{T2}'
|
78
|
+
# Read the tag list:
|
79
|
+
tags = `herbstclient tag_status #{monitor}`.chomp.split("\t").drop(1)
|
80
|
+
tagline[monitor] << parse_hlwm_tags(tags, monitor) << '%{T1}'
|
81
|
+
end
|
82
|
+
tagline
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse_hlwm_tags(tags, monitor)
|
86
|
+
line = ''
|
87
|
+
tags.each do |tag|
|
88
|
+
status, name = tag.slice!(0), tag
|
89
|
+
vars = { monitor: monitor, tag: name,
|
90
|
+
icon: @tag_icons[name] }.merge(tag_color(status))
|
91
|
+
line << format('%%{B%<bg>s}%%{F%<fg>s}%%{A:herbstclient chain .-. '\
|
92
|
+
'focus_monitor %<monitor>s .-. use %<tag>s:}'\
|
93
|
+
' %<icon>s %%{A}', vars)
|
94
|
+
end
|
95
|
+
line
|
96
|
+
end
|
97
|
+
|
98
|
+
def parse_bspwm_tags(tags)
|
99
|
+
tagline = {}
|
100
|
+
monitor = ''
|
101
|
+
tags.each do |tag|
|
102
|
+
next if tag[0] =~ /[LTG]/
|
103
|
+
|
104
|
+
status, name = tag.slice!(0), tag
|
105
|
+
if status.casecmp?('m')
|
106
|
+
monitor = name
|
107
|
+
tagline[monitor] = ''
|
108
|
+
next unless @monitors.count > 1
|
109
|
+
|
110
|
+
vars = { name: name }.merge(tag_color(status))
|
111
|
+
tagline[monitor] << format('%%{B%<bg>s}%%{F%<fg>s}'\
|
112
|
+
'%%{A:bspc monitor -f %<name>s:}'\
|
113
|
+
' %<name>s %%{A}', vars)
|
114
|
+
else
|
115
|
+
vars = { name: name, icon: @tag_icons[name] || name }.merge(tag_color(status))
|
116
|
+
tagline[monitor] << format('%%{B%<bg>s}%%{F%<fg>s}'\
|
117
|
+
'%%{A:bspc desktop -f %<name>s:}'\
|
118
|
+
' %<icon>s %%{A}', vars)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
tagline
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.get_monitors(wmname = ENV['XDG_SESSION_DESKTOP'])
|
125
|
+
case wmname
|
126
|
+
when 'herbstluftwm'
|
127
|
+
`herbstclient list_monitors`.scan(/^\d+/)
|
128
|
+
when 'bspwm'
|
129
|
+
`bspc query -M --names`.split("\n")
|
130
|
+
else
|
131
|
+
raise NameError, 'Unknown WM!'
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Barabara
|
3
|
+
module Modules
|
4
|
+
class WindowName
|
5
|
+
include Wisper::Publisher
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@cmd = 'xtitle -sf "%s\n"'
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.limit(line)
|
12
|
+
line.length > 80 ? line[0..60].gsub(/\s\w+\s*$/, '…') : line
|
13
|
+
end
|
14
|
+
|
15
|
+
def watch
|
16
|
+
PTY.spawn(@cmd) do |stdout, _stdin, pid|
|
17
|
+
stdout.each do |line|
|
18
|
+
title = WindowName.limit(line.chomp)
|
19
|
+
publish(:event, 'window_title', title)
|
20
|
+
end
|
21
|
+
Process.wait pid
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Barabara
|
5
|
+
module Modules
|
6
|
+
class Wttr
|
7
|
+
include Wisper::Publisher
|
8
|
+
require 'net/http'
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
options = GlobalConfig.config.module_config('weather')
|
12
|
+
@colors = GlobalConfig.config.colors
|
13
|
+
@location = options['location'] || 'London'
|
14
|
+
@unit = (options['unit'] || 'c') == 'c' ? 'm' : 'u'
|
15
|
+
@format = options['format']
|
16
|
+
@uri = format_uri
|
17
|
+
end
|
18
|
+
|
19
|
+
def format_uri
|
20
|
+
uri = URI("https://wttr.in/#{@location}")
|
21
|
+
query = URI.encode_www_form(@unit => nil, 'format' => "%c\t%t")
|
22
|
+
uri.query = query
|
23
|
+
uri
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch
|
27
|
+
Net::HTTP.get_response(@uri)
|
28
|
+
rescue SocketError
|
29
|
+
'⌚'
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse!
|
33
|
+
@icon, rawtemp = @raw_data.body.split("\t")
|
34
|
+
@temp = rawtemp.to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
def render
|
38
|
+
@raw_data = fetch
|
39
|
+
return '⌚' unless @raw_data.is_a?(Net::HTTPSuccess)
|
40
|
+
|
41
|
+
parse!
|
42
|
+
sign = '+' if @temp.positive?
|
43
|
+
format(
|
44
|
+
@format,
|
45
|
+
{ temp: "#{sign}#{@temp}", icon: @icon }.merge(@colors)
|
46
|
+
).force_encoding('utf-8')
|
47
|
+
end
|
48
|
+
|
49
|
+
def watch
|
50
|
+
loop do
|
51
|
+
publish(:event, 'weather', render)
|
52
|
+
sleep 900
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: barabara
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Serge Tkatchouk
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-12-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: wisper
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: optimist
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
executables:
|
72
|
+
- barabara
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- Gemfile
|
77
|
+
- bin/barabara
|
78
|
+
- lib/barabara.rb
|
79
|
+
- lib/barabara/app.rb
|
80
|
+
- lib/barabara/config.rb
|
81
|
+
- lib/barabara/modules/battery.rb
|
82
|
+
- lib/barabara/modules/clock.rb
|
83
|
+
- lib/barabara/modules/event_processor.rb
|
84
|
+
- lib/barabara/modules/lemonbar.rb
|
85
|
+
- lib/barabara/modules/volume.rb
|
86
|
+
- lib/barabara/modules/weather.rb
|
87
|
+
- lib/barabara/modules/wm.rb
|
88
|
+
- lib/barabara/modules/wname.rb
|
89
|
+
- lib/barabara/modules/wttr.rb
|
90
|
+
- lib/barabara/version.rb
|
91
|
+
homepage: https://github.com/spijet/barabara
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.3'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements:
|
110
|
+
- lemonbar
|
111
|
+
rubygems_version: 3.0.6
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: Bar brains for standalone WMs
|
115
|
+
test_files: []
|