barabara 0.0.3

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