barabara 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|