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.
@@ -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
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'barabara'
4
+
5
+ # Don't panic on SIGINT
6
+ trap('INT') do
7
+ puts 'Interrupt caught, exiting.'
8
+ exit 0
9
+ end
10
+
11
+ Barabara.run_app
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Barabara
2
+ VERSION = '0.0.3'.freeze
3
+ 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: []