added some keyboard shortcuts to README and lots of cleanup

This commit is contained in:
2021-07-15 13:06:48 -04:00
parent 8e825dbde4
commit 68f85b91b8
57 changed files with 110 additions and 2091 deletions

View File

@@ -1,124 +0,0 @@
-------------------------------------------------
-- Battery Widget for Awesome Window Manager
-- Shows the battery status using the ACPI tool
-- More details could be found here:
-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/battery-widget
-- @author Pavel Makhov
-- @copyright 2017 Pavel Makhov
-------------------------------------------------
local awful = require('awful')
local watch = require('awful.widget.watch')
local wibox = require('wibox')
local beautiful = require('beautiful')
local dpi = require('beautiful').xresources.apply_dpi
-- acpi sample outputs
-- Battery 0: Discharging, 75%, 01:51:38 remaining
-- Battery 0: Charging, 53%, 00:57:43 until charged
local percentage = wibox.widget.textbox()
local battery_icon = wibox.widget.textbox()
battery_icon.font = beautiful.icon_font
local battery_popup = awful.tooltip({
objects = {percentage},
mode = 'outside',
align = 'left',
preferred_positions = {'right', 'left', 'top', 'bottom'}
})
watch('acpi -i', 10, function(_, stdout)
local battery_info = {}
local capacities = {}
for s in stdout:gmatch('[^\r\n]+') do
local status, charge_str, time = string.match(s, '.+: (%a+), (%d?%d?%d)%%,?.*')
if status ~= nil then
table.insert(battery_info, {
status = status,
charge = tonumber(charge_str)
})
else
local cap_str = string.match(s, '.+:.+last full capacity (%d+)')
table.insert(capacities, tonumber(cap_str))
end
end
local capacity = 0
for _, cap in ipairs(capacities) do
capacity = capacity + cap
end
local charge = 0
local status
for i, batt in ipairs(battery_info) do
if batt.charge >= charge then
status = batt.status -- use most charged battery status
-- this is arbitrary, and maybe another metric should be used
end
charge = charge + batt.charge * capacities[i]
end
charge = charge / capacity
battery_popup.text = string.gsub(stdout, '\n$', '')
percentage.text = math.floor(charge)
if status == 'Charging' then
battery_icon.text = ''
if math.floor(charge) <= 20 then
battery_icon.text = ''
elseif math.floor(charge) <= 30 then
battery_icon.text = ''
elseif math.floor(charge) <= 40 then
battery_icon.text = ''
elseif math.floor(charge) <= 60 then
battery_icon.text = ''
elseif math.floor(charge) <= 80 then
battery_icon.text = ''
elseif math.floor(charge) <= 90 then
--battery_icon.text = ''
battery_icon.text = '90'
elseif math.floor(charge) <= 100 then
--battery_icon.text = ''
battery_icon.text = '90'
end
elseif status == 'Full' then
battery_icon.text = ''
else
if math.floor(charge) <= 10 then
battery_icon.text = ''
elseif math.floor(charge) <= 20 then
battery_icon.text = ''
elseif math.floor(charge) <= 30 then
battery_icon.text = ''
elseif math.floor(charge) <= 40 then
battery_icon.text = ''
elseif math.floor(charge) <= 50 then
battery_icon.text = ''
elseif math.floor(charge) <= 60 then
battery_icon.text = ''
elseif math.floor(charge) <= 60 then
battery_icon.text = ''
elseif math.floor(charge) <= 80 then
battery_icon.text = ''
elseif math.floor(charge) <= 90 then
battery_icon.text = ''
battery_icon.text = '90'
elseif math.floor(charge) <= 100 then
battery_icon.text = ''
battery_icon.text = '90'
end
end
collectgarbage('collect')
end)
return wibox.widget {
wibox.widget{
battery_icon,
fg = beautiful.accent.hue_300,
widget = wibox.container.background
},
percentage,
spacing = dpi(2),
layout = wibox.layout.fixed.horizontal
}

View File

@@ -1,52 +0,0 @@
local awful = require("awful")
local wibox = require('wibox')
local mat_list_item = require('widget.material.list-item')
local dpi = require('beautiful').xresources.apply_dpi
local watch = require('awful.widget.watch')
local beautiful = require('beautiful')
local battery_icon = wibox.widget.textbox()
battery_icon.font = beautiful.icon_font
local battery_widget = wibox.widget.textbox()
battery_widget.align = 'center'
battery_widget.valign = 'center'
battery_widget.font = beautiful.font
local battery
function update_battery()
awful.spawn.easy_async_with_shell("pamixer --get-volume", function(stdout)
battery = stdout
--battery = string.match(stdout, '(%d?%d?%d)%%')
awful.spawn.easy_async_with_shell("bash -c 'pacmd list-sinks | awk '/muted/ { print $2 }''", function(muted)
--muted = string.gsub(muted, "%s+", "")
muted = 'no'
if muted == 'muted:no' and (battery > '50' or battery == '100') then
battery_icon.text = ''
elseif muted == 'muted:no' and battery <= '50' and battery > '0' then
battery_icon.text = '奔'
elseif muted == 'muted:yes' then
battery_icon.text = ''
elseif battery == '0' then
battery_icon.text = ''
end
battery_widget.text = battery
end)
collectgarbage('collect')
end)
end
watch('bash -c', 3, function(_, stdout)
update_battery()
end)
return wibox.widget {
wibox.widget{
battery_icon,
fg = beautiful.accent.hue_100,
widget = wibox.container.background
},
battery_widget,
spacing = dpi(2),
layout = wibox.layout.fixed.horizontal
}

View File

@@ -1,191 +0,0 @@
-------------------------------------------------
-- Battery Widget for Awesome Window Manager
-- Shows the battery status using the ACPI tool
-- More details could be found here:
-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/battery-widget
-- @author Pavel Makhov
-- @copyright 2017 Pavel Makhov
-------------------------------------------------
local awful = require("awful")
local naughty = require("naughty")
local watch = require("awful.widget.watch")
local wibox = require("wibox")
local gfs = require("gears.filesystem")
local dpi = require('beautiful').xresources.apply_dpi
-- acpi sample outputs
-- Battery 0: Discharging, 75%, 01:51:38 remaining
-- Battery 0: Charging, 53%, 00:57:43 until charged
local HOME = os.getenv("HOME")
local battery_widget = {}
local function worker(args)
local args = args or {}
local bat_num = '1'
local font = args.font or 'Play 8'
local path_to_icons = args.path_to_icons or "/usr/share/icons/Arc/status/symbolic/"
local show_current_level = args.show_current_level or false
local margin_left = args.margin_left or 0
local margin_right = args.margin_right or 0
local display_notification = args.display_notification or false
local position = args.notification_position or "top_right"
local timeout = args.timeout or 10
local warning_msg_title = args.warning_msg_title or 'Huston, we have a problem'
local warning_msg_text = args.warning_msg_text or 'Battery is dying'
local warning_msg_position = args.warning_msg_position or 'bottom_right'
local warning_msg_icon = args.warning_msg_icon or HOME .. '/.config/awesome/awesome-wm-widgets/batteryarc-widget/spaceman.jpg'
local enable_battery_warning = args.enable_battery_warning
if enable_battery_warning == nil then
enable_battery_warning = true
end
local bat_expr = 'Battery ' .. bat_num
if not gfs.dir_readable(path_to_icons) then
naughty.notify{
title = "Battery Widget",
text = "Folder with icons doesn't exist: " .. path_to_icons,
preset = naughty.config.presets.critical
}
end
local icon_widget = wibox.widget {
{
id = "icon",
widget = wibox.widget.imagebox,
resize = false
},
layout = wibox.container.margin(_, 0, 0, 3)
}
local level_widget = wibox.widget {
font = font,
widget = wibox.widget.textbox
}
battery_widget = wibox.widget {
icon_widget,
level_widget,
layout = wibox.layout.fixed.horizontal,
}
-- Popup with battery info
-- One way of creating a pop-up notification - naughty.notify
local notification
local function show_battery_status(batteryType)
awful.spawn.easy_async('bash -c "acpi | grep \'' .. bat_expr .. '\'"',
function(stdout, _, _, _)
naughty.destroy(notification)
notification = naughty.notify{
text = stdout,
title = "Battery status",
icon = path_to_icons .. batteryType .. ".svg",
icon_size = dpi(16),
position = position,
timeout = 5, hover_timeout = 0.5,
width = 200,
screen = mouse.screen
}
end
)
end
-- Alternative to naughty.notify - tooltip. You can compare both and choose the preferred one
--battery_popup = awful.tooltip({objects = {battery_widget}})
-- To use colors from beautiful theme put
-- following lines in rc.lua before require("battery"):
-- beautiful.tooltip_fg = beautiful.fg_normal
-- beautiful.tooltip_bg = beautiful.bg_normal
local function show_battery_warning()
naughty.notify {
icon = warning_msg_icon,
icon_size = 100,
text = warning_msg_text,
title = warning_msg_title,
timeout = 25, -- show the warning for a longer time
hover_timeout = 0.5,
position = warning_msg_position,
bg = "#F06060",
fg = "#EEE9EF",
width = 300,
screen = mouse.screen
}
end
local last_battery_check = os.time()
local batteryType = "battery-good-symbolic"
watch('bash -c "acpi -i | grep \'' .. bat_expr .. '\'"', timeout,
function(widget, stdout, stderr, exitreason, exitcode)
local battery_info = {}
local capacities = {}
for s in stdout:gmatch("[^\r\n]+") do
local status, charge_str, time = string.match(s, '.+: (%a+), (%d?%d?%d)%%,?(.*)')
if status ~= nil then
table.insert(battery_info, {status = status, charge = tonumber(charge_str)})
else
local cap_str = string.match(s, '.+:.+last full capacity (%d+)')
table.insert(capacities, tonumber(cap_str))
end
end
local capacity = 0
for i, cap in ipairs(capacities) do
capacity = capacity + cap
end
local charge = 0
local status
for i, batt in ipairs(battery_info) do
if batt.charge >= charge then
status = batt.status -- use most charged battery status
-- this is arbitrary, and maybe another metric should be used
end
charge = charge + batt.charge * capacities[i]
end
charge = charge / capacity
if show_current_level then
level_widget.text = string.format('%d%%', charge)
end
if (charge >= 0 and charge < 15) then
batteryType = "battery-empty%s-symbolic"
if enable_battery_warning and status ~= 'Charging' and os.difftime(os.time(), last_battery_check) > 300 then
-- if 5 minutes have elapsed since the last warning
last_battery_check = os.time()
show_battery_warning()
end
elseif (charge >= 15 and charge < 40) then batteryType = "battery-caution%s-symbolic"
elseif (charge >= 40 and charge < 60) then batteryType = "battery-low%s-symbolic"
elseif (charge >= 60 and charge < 80) then batteryType = "battery-good%s-symbolic"
elseif (charge >= 80 and charge <= 100) then batteryType = "battery-full%s-symbolic"
end
if status == 'Charging' then
batteryType = string.format(batteryType, '-charging')
else
batteryType = string.format(batteryType, '')
end
widget.icon:set_image(path_to_icons .. batteryType .. ".svg")
-- Update popup text
-- battery_popup.text = string.gsub(stdout, "\n$", "")
end,
icon_widget)
if display_notification then
battery_widget:connect_signal("mouse::enter", function() show_battery_status(batteryType) end)
battery_widget:connect_signal("mouse::leave", function() naughty.destroy(notification) end)
end
return wibox.container.margin(battery_widget, margin_left, margin_right)
end
return setmetatable(battery_widget, { __call = function(_, ...) return worker(...) end })

View File

@@ -1,53 +0,0 @@
# Calendar Widget
Calendar widget for Awesome WM - slightly improved version of the `wibox.widget.calendar`.
## Features
- mouse support: scroll up - shows next month, scroll down - previous
- themes:
| Name | Screenshot |
|---|---|
|nord (default) | ![nord_theme](./nord.png) |
| outrun | ![outrun_theme](./outrun.png) |
| light | ![outrun_theme](./light.png) |
| dark | ![outrun_theme](./dark.png) |
- setup widget placement
top center - in case you clock is centered:
![calendar_top](./calendar_top.png)
top right - for default awesome config:
![calendar_top_right](./calendar_top_right.png)
bottom right - in case your wibar at the bottom:
![calendar_bottom_right](./calendar_bottom_right.png)
## How to use
This widget needs an 'anchor' - another widget which triggers visibility of the calendar. Default `mytextclock` is the perfect candidate!
Just after mytextclock is instantiated, create the widget and add the mouse listener to it.
```lua
local calendar_widget = require("awesome-wm-widgets.calendar-widget.calendar")
-- ...
-- Create a textclock widget
mytextclock = wibox.widget.textclock()
-- default
local cw = calendar_widget()
-- or customized
local cw = calendar_widget({
theme = 'outrun',
placement = 'bottom_right'
})
mytextclock:connect_signal("button::press",
function(_, _, _, button)
if button == 1 then cw.toggle() end
end)
```

View File

@@ -1,235 +0,0 @@
-------------------------------------------------
-- Calendar Widget for Awesome Window Manager
-- Shows the current month and supports scroll up/down to switch month
-- More details could be found here:
-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/calendar-widget
-- @author Pavel Makhov
-- @copyright 2019 Pavel Makhov
-------------------------------------------------
local awful = require("awful")
local beautiful = require("beautiful")
local wibox = require("wibox")
local gears = require("gears")
local naughty = require("naughty")
local calendar_widget = {}
local function worker(args)
local calendar_themes = {
nord = {
bg = '#2E3440',
fg = '#D8DEE9',
focus_date_bg = '#88C0D0',
focus_date_fg = '#000000',
weekend_day_bg = '#3B4252',
weekday_fg = '#88C0D0',
header_fg = '#E5E9F0',
border = '#4C566A'
},
outrun = {
bg = '#0d0221',
fg = '#D8DEE9',
focus_date_bg = '#650d89',
focus_date_fg = '#2de6e2',
weekend_day_bg = '#261447',
weekday_fg = '#2de6e2',
header_fg = '#f6019d',
border = '#261447'
},
dark = {
bg = '#000000',
fg = '#ffffff',
focus_date_bg = '#ffffff',
focus_date_fg = '#000000',
weekend_day_bg = '#444444',
weekday_fg = '#ffffff',
header_fg = '#ffffff',
border = '#333333'
},
light = {
bg = '#ffffff',
fg = '#000000',
focus_date_bg = '#000000',
focus_date_fg = '#ffffff',
weekend_day_bg = '#AAAAAA',
weekday_fg = '#000000',
header_fg = '#000000',
border = '#CCCCCC'
},
monokai = {
bg = '#272822',
fg = '#F8F8F2',
focus_date_bg = '#AE81FF',
focus_date_fg = '#ffffff',
weekend_day_bg = '#75715E',
weekday_fg = '#FD971F',
header_fg = '#F92672',
border = '#75715E'
}
}
local args = args or {}
if args.theme ~= nil and calendar_themes[args.theme] == nil then
naughty.notify({
preset = naughty.config.presets.critical,
title = 'Calendar Widget',
text = 'Theme "' .. args.theme .. '" not found, fallback to default'})
args.theme = 'nord'
end
local theme = args.theme or 'nord'
local placement = args.placement or 'top'
local styles = {}
local function rounded_shape(size)
return function(cr, width, height)
gears.shape.rounded_rect(cr, width, height, size)
end
end
styles.month = {
padding = 4,
bg_color = calendar_themes[theme].bg,
border_width = 0,
}
styles.normal = {
markup = function(t) return t end,
shape = rounded_shape(4)
}
styles.focus = {
fg_color = calendar_themes[theme].focus_date_fg,
bg_color = calendar_themes[theme].focus_date_bg,
markup = function(t) return '<b>' .. t .. '</b>' end,
shape = rounded_shape(4)
}
styles.header = {
fg_color = calendar_themes[theme].header_fg,
bg_color = calendar_themes[theme].bg,
markup = function(t) return '<b>' .. t .. '</b>' end
}
styles.weekday = {
fg_color = calendar_themes[theme].weekday_fg,
bg_color = calendar_themes[theme].bg,
markup = function(t) return '<b>' .. t .. '</b>' end,
}
local function decorate_cell(widget, flag, date)
if flag == 'monthheader' and not styles.monthheader then
flag = 'header'
end
-- highlight only today's day
if flag == 'focus' then
local today = os.date('*t')
if today.month ~= date.month then
flag = 'normal'
end
end
local props = styles[flag] or {}
if props.markup and widget.get_text and widget.set_markup then
widget:set_markup(props.markup(widget:get_text()))
end
-- Change bg color for weekends
local d = { year = date.year, month = (date.month or 1), day = (date.day or 1) }
local weekday = tonumber(os.date('%w', os.time(d)))
local default_bg = (weekday == 0 or weekday == 6) and calendar_themes[theme].weekend_day_bg or calendar_themes[theme].bg
local ret = wibox.widget {
{
{
widget,
halign = 'center',
widget = wibox.container.place
},
margins = (props.padding or 2) + (props.border_width or 0),
widget = wibox.container.margin
},
shape = props.shape,
shape_border_color = props.border_color or '#000000',
shape_border_width = props.border_width or 0,
fg = props.fg_color or calendar_themes[theme].fg,
bg = props.bg_color or default_bg,
widget = wibox.container.background
}
return ret
end
local cal = wibox.widget {
date = os.date('*t'),
font = beautiful.get_font(),
fn_embed = decorate_cell,
long_weekdays = true,
widget = wibox.widget.calendar.month
}
local popup = awful.popup {
ontop = true,
visible = false,
shape = gears.shape.rounded_rect,
offset = { y = 5 },
border_width = 1,
border_color = calendar_themes[theme].border,
widget = cal
}
popup:buttons(
awful.util.table.join(
awful.button({}, 4, function()
local a = cal:get_date()
a.month = a.month + 1
cal:set_date(nil)
cal:set_date(a)
popup:set_widget(cal)
end),
awful.button({}, 5, function()
local a = cal:get_date()
a.month = a.month - 1
cal:set_date(nil)
cal:set_date(a)
popup:set_widget(cal)
end)
)
)
function calendar_widget.toggle()
if popup.visible then
-- to faster render the calendar refresh it and just hide
cal:set_date(nil) -- the new date is not set without removing the old one
cal:set_date(os.date('*t'))
popup:set_widget(nil) -- just in case
popup:set_widget(cal)
popup.visible = not popup.visible
else
if placement == 'top' then
awful.placement.top(popup, { margins = { top = 30 }, parent = awful.screen.focused() })
elseif placement == 'top_right' then
awful.placement.top_right(popup, { margins = { top = 30, right = 10}, parent = awful.screen.focused() })
elseif placement == 'bottom_right' then
awful.placement.bottom_right(popup, { margins = { bottom = 30, right = 10}, parent = awful.screen.focused() })
else
awful.placement.top(popup, { margins = { top = 30 }, parent = awful.screen.focused() })
end
popup.visible = true
end
end
return calendar_widget
end
return setmetatable(calendar_widget, { __call = function(_, ...)
return worker(...)
end })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

View File

@@ -1,70 +0,0 @@
local awful = require('awful')
local gears = require('gears')
local wibox = require('wibox')
local beautiful = require('beautiful')
local icons = require('theme.icons')
local clickable_container = require('widget.material.clickable-container')
local dpi = require('beautiful').xresources.apply_dpi
local styles = {}
local function rounded_shape(size, partial)
if partial then
return function(cr, width, height)
gears.shape.rectangle(cr, width + 5, height, 11)
end
else
return function(cr, width, height)
gears.shape.rectangle(cr, width, height, size)
end
end
end
styles.month = {padding = 5, bg_color = '#555555', shape = rounded_shape(10)}
styles.normal = {shape = rounded_shape(5)}
styles.focus = {
fg_color = beautiful.primary.hue_400, -- Current day Color
markup = function(t) return '<b>' .. t .. '</b>' end,
shape = rounded_shape(5, true)
}
styles.header = {
fg_color = beautiful.primary.hue_200, -- Month Name Color
markup = function(t) return '<b>' .. t .. '</b>' end,
shape = rounded_shape(10)
}
styles.weekday = {
fg_color = beautiful.background.hue_50, -- Day Color
markup = function(t) return '<b>' .. t .. '</b>' end,
shape = rounded_shape(5)
}
local function decorate_cell(widget, flag, date)
if flag == 'monthheader' and not styles.monthheader then flag = 'header' end
local props = styles[flag] or {}
if props.markup and widget.get_text and widget.set_markup then
widget:set_markup(props.markup(widget:get_text()))
end
local d = {
year = date.year,
month = (date.month or 1),
day = (date.day or 1)
}
local weekday = tonumber(os.date('%w', os.time(d)))
local ret = wibox.widget {
{
widget,
margins = (props.padding or 2) + (props.border_width or 0),
widget = wibox.container.margin
},
fg = props.fg_color or '#999999',
widget = wibox.container.background
}
return ret
end
local cal = wibox.widget {
date = os.date('*t'),
font = 'Roboto 10',
fn_embed = decorate_cell,
start_sunday = true,
widget = wibox.widget.calendar.month
}
return cal

View File

@@ -8,7 +8,7 @@ local dpi = require('beautiful').xresources.apply_dpi
local wibox = require('wibox')
local gears = require('gears')
local beautiful = require('beautiful')
local mat_colors = require('theme.mat-colors')
local mat_colors = require('theme.color-schemes')
-- Local declarations
local mat_slider = {

View File

@@ -1,560 +0,0 @@
-------------------------------------------------
-- Weather Widget based on the OpenWeatherMap
-- https://openweathermap.org/
--
-- @author Pavel Makhov
-- @copyright 2020 Pavel Makhov
-------------------------------------------------
local awful = require("awful")
local watch = require("awful.widget.watch")
local json = require("json")
local naughty = require("naughty")
local wibox = require("wibox")
local gears = require("gears")
local beautiful = require("beautiful")
local HOME_DIR = os.getenv("HOME")
local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/weather-widget'
local GET_FORECAST_CMD = [[bash -c "curl -s --show-error -X GET '%s'"]]
local function show_warning(message)
naughty.notify {
preset = naughty.config.presets.critical,
title = 'Weather Widget',
text = message
}
end
local weather_widget = {}
local warning_shown = false
local tooltip = awful.tooltip {
mode = 'outside',
preferred_positions = {'bottom'}
}
local weather_popup = awful.popup {
ontop = true,
visible = false,
shape = gears.shape.rounded_rect,
border_width = 1,
border_color = beautiful.bg_focus,
maximum_width = 400,
offset = {y = 5},
widget = {}
}
--- Maps openWeatherMap icon name to file name w/o extension
local icon_map = {
["01d"] = "clear-sky",
["02d"] = "few-clouds",
["03d"] = "scattered-clouds",
["04d"] = "broken-clouds",
["09d"] = "shower-rain",
["10d"] = "rain",
["11d"] = "thunderstorm",
["13d"] = "snow",
["50d"] = "mist",
["01n"] = "clear-sky-night",
["02n"] = "few-clouds-night",
["03n"] = "scattered-clouds-night",
["04n"] = "broken-clouds-night",
["09n"] = "shower-rain-night",
["10n"] = "rain-night",
["11n"] = "thunderstorm-night",
["13n"] = "snow-night",
["50n"] = "mist-night"
}
--- Return wind direction as a string
local function to_direction(degrees)
-- Ref: https://www.campbellsci.eu/blog/convert-wind-directions
if degrees == nil then return "Unknown dir" end
local directions = {
"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW",
"WSW", "W", "WNW", "NW", "NNW", "N"
}
return directions[math.floor((degrees % 360) / 22.5) + 1]
end
--- Convert degrees Celsius to Fahrenheit
local function celsius_to_fahrenheit(c) return c * 9 / 5 + 32 end
-- Convert degrees Fahrenheit to Celsius
local function fahrenheit_to_celsius(f) return (f - 32) * 5 / 9 end
local function gen_temperature_str(temp, fmt_str, show_other_units, units)
local temp_str = string.format(fmt_str, temp)
local s = temp_str .. '°' .. (units == 'metric' and 'C' or 'F')
if (show_other_units) then
local temp_conv, units_conv
if (units == 'metric') then
temp_conv = celsius_to_fahrenheit(temp)
units_conv = 'F'
else
temp_conv = fahrenheit_to_celsius(temp)
units_conv = 'C'
end
local temp_conv_str = string.format(fmt_str, temp_conv)
s = s .. ' ' .. '(' .. temp_conv_str .. '°' .. units_conv .. ')'
end
return s
end
local function uvi_index_color(uvi)
local color
if uvi >= 0 and uvi < 3 then color = '#A3BE8C'
elseif uvi >= 3 and uvi < 6 then color = '#EBCB8B'
elseif uvi >= 6 and uvi < 8 then color = '#D08770'
elseif uvi >= 8 and uvi < 11 then color = '#BF616A'
elseif uvi >= 11 then color = '#B48EAD'
end
return '<span weight="bold" foreground="' .. color .. '">' .. uvi .. '</span>'
end
local function worker(user_args)
local args = user_args or {}
--- Validate required parameters
if args.coordinates == nil or args.api_key == nil then
show_warning('Required parameters are not set: ' ..
(args.coordinates == nil and '<b>coordinates</b>' or '') ..
(args.api_key == nil and ', <b>api_key</b> ' or ''))
return
end
local coordinates = args.coordinates
local api_key = args.api_key
local font_name = args.font_name or beautiful.font:gsub("%s%d+$", "")
local units = args.units or 'metric'
local time_format_12h = args.time_format_12h
local both_units_widget = args.both_units_widget or false
local show_hourly_forecast = args.show_hourly_forecast
local show_daily_forecast = args.show_daily_forecast
local icon_pack_name = args.icons or 'weather-underground-icons'
local icons_extension = args.icons_extension or '.png'
local timeout = args.timeout or 120
local ICONS_DIR = WIDGET_DIR .. '/icons/' .. icon_pack_name .. '/'
local owm_one_cal_api =
('https://api.openweathermap.org/data/2.5/onecall' ..
'?lat=' .. coordinates[1] .. '&lon=' .. coordinates[2] .. '&appid=' .. api_key ..
'&units=' .. units .. '&exclude=minutely' ..
(show_hourly_forecast == false and ',hourly' or '') ..
(show_daily_forecast == false and ',daily' or ''))
weather_widget = wibox.widget {
{
{
{
{
id = 'icon',
resize = true,
widget = wibox.widget.imagebox
},
valign = 'center',
widget = wibox.container.place,
},
{
id = 'txt',
widget = wibox.widget.textbox
},
layout = wibox.layout.fixed.horizontal,
},
margins = 4,
layout = wibox.container.margin
},
shape = function(cr, width, height)
gears.shape.rounded_rect(cr, width, height, 4)
end,
widget = wibox.container.background,
set_image = function(self, path)
self:get_children_by_id('icon')[1].image = path
end,
set_text = function(self, text)
self:get_children_by_id('txt')[1].text = text
end,
is_ok = function(self, is_ok)
if is_ok then
self:get_children_by_id('icon')[1]:set_opacity(1)
self:get_children_by_id('icon')[1]:emit_signal('widget:redraw_needed')
else
self:get_children_by_id('icon')[1]:set_opacity(0.2)
self:get_children_by_id('icon')[1]:emit_signal('widget:redraw_needed')
end
end
}
local current_weather_widget = wibox.widget {
{
{
{
id = 'icon',
resize = true,
forced_width = 128,
forced_height = 128,
widget = wibox.widget.imagebox
},
align = 'center',
widget = wibox.container.place
},
{
id = 'description',
font = font_name .. ' 10',
align = 'center',
widget = wibox.widget.textbox
},
forced_width = 128,
layout = wibox.layout.align.vertical
},
{
{
{
id = 'temp',
font = font_name .. ' 36',
widget = wibox.widget.textbox
},
{
id = 'feels_like_temp',
align = 'center',
font = font_name .. ' 9',
widget = wibox.widget.textbox
},
layout = wibox.layout.fixed.vertical
},
{
{
id = 'wind',
font = font_name .. ' 9',
widget = wibox.widget.textbox
},
{
id = 'humidity',
font = font_name .. ' 9',
widget = wibox.widget.textbox
},
{
id = 'uv',
font = font_name .. ' 9',
widget = wibox.widget.textbox
},
expand = 'inside',
layout = wibox.layout.align.vertical
},
spacing = 16,
forced_width = 150,
layout = wibox.layout.fixed.vertical
},
forced_width = 300,
layout = wibox.layout.flex.horizontal,
update = function(self, weather)
self:get_children_by_id('icon')[1]:set_image(
ICONS_DIR .. icon_map[weather.weather[1].icon] .. icons_extension)
self:get_children_by_id('temp')[1]:set_text(gen_temperature_str(weather.temp, '%.0f', false, units))
self:get_children_by_id('feels_like_temp')[1]:set_text(
'Feels like ' .. gen_temperature_str(weather.feels_like, '%.0f', false, units))
self:get_children_by_id('description')[1]:set_text(weather.weather[1].description)
self:get_children_by_id('wind')[1]:set_markup(
'Wind: <b>' .. weather.wind_speed .. 'm/s (' .. to_direction(weather.wind_deg) .. ')</b>')
self:get_children_by_id('humidity')[1]:set_markup('Humidity: <b>' .. weather.humidity .. '%</b>')
self:get_children_by_id('uv')[1]:set_markup('UV: ' .. uvi_index_color(weather.uvi))
end
}
local daily_forecast_widget = {
forced_width = 300,
layout = wibox.layout.flex.horizontal,
update = function(self, forecast, timezone_offset)
local count = #self
for i = 0, count do self[i]=nil end
for i, day in ipairs(forecast) do
if i > 5 then break end
local day_forecast = wibox.widget {
{
text = os.date('%a', tonumber(day.dt) + tonumber(timezone_offset)),
align = 'center',
font = font_name .. ' 9',
widget = wibox.widget.textbox
},
{
{
{
image = ICONS_DIR .. icon_map[day.weather[1].icon] .. icons_extension,
resize = true,
forced_width = 48,
forced_height = 48,
widget = wibox.widget.imagebox
},
align = 'center',
layout = wibox.container.place
},
{
text = day.weather[1].description,
font = font_name .. ' 8',
align = 'center',
forced_height = 50,
widget = wibox.widget.textbox
},
layout = wibox.layout.fixed.vertical
},
{
{
text = gen_temperature_str(day.temp.day, '%.0f', false, units),
align = 'center',
font = font_name .. ' 9',
widget = wibox.widget.textbox
},
{
text = gen_temperature_str(day.temp.night, '%.0f', false, units),
align = 'center',
font = font_name .. ' 9',
widget = wibox.widget.textbox
},
layout = wibox.layout.fixed.vertical
},
spacing = 8,
layout = wibox.layout.fixed.vertical
}
table.insert(self, day_forecast)
end
end
}
local hourly_forecast_graph = wibox.widget {
step_width = 12,
color = '#EBCB8B',
background_color = beautiful.bg_normal,
forced_height = 100,
forced_width = 300,
widget = wibox.widget.graph,
set_max_value = function(self, new_max_value)
self.max_value = new_max_value
end,
set_min_value = function(self, new_min_value)
self.min_value = new_min_value
end
}
local hourly_forecast_negative_graph = wibox.widget {
step_width = 12,
color = '#5E81AC',
background_color = beautiful.bg_normal,
forced_height = 100,
forced_width = 300,
widget = wibox.widget.graph,
set_max_value = function(self, new_max_value)
self.max_value = new_max_value
end,
set_min_value = function(self, new_min_value)
self.min_value = new_min_value
end
}
local hourly_forecast_widget = {
layout = wibox.layout.fixed.vertical,
update = function(self, hourly)
local hours_below = {
id = 'hours',
forced_width = 300,
layout = wibox.layout.flex.horizontal
}
local temp_below = {
id = 'temp',
forced_width = 300,
layout = wibox.layout.flex.horizontal
}
local max_temp = -1000
local min_temp = 1000
local values = {}
for i, hour in ipairs(hourly) do
if i > 25 then break end
values[i] = hour.temp
if max_temp < hour.temp then max_temp = hour.temp end
if min_temp > hour.temp then min_temp = hour.temp end
if (i - 1) % 5 == 0 then
table.insert(hours_below, wibox.widget {
text = os.date(time_format_12h and '%I%p' or '%H:00', tonumber(hour.dt)),
align = 'center',
font = font_name .. ' 9',
widget = wibox.widget.textbox
})
table.insert(temp_below, wibox.widget {
markup = '<span foreground="'
.. (tonumber(hour.temp) > 0 and '#2E3440' or '#ECEFF4') .. '">'
.. string.format('%.0f', hour.temp) .. '°' .. '</span>',
align = 'center',
font = font_name .. ' 9',
widget = wibox.widget.textbox
})
end
end
hourly_forecast_graph:set_max_value(math.max(max_temp, math.abs(min_temp)))
hourly_forecast_graph:set_min_value(min_temp > 0 and min_temp * 0.7 or 0) -- move graph a bit up
hourly_forecast_negative_graph:set_max_value(math.abs(min_temp))
hourly_forecast_negative_graph:set_min_value(max_temp < 0 and math.abs(max_temp) * 0.7 or 0)
for _, value in ipairs(values) do
if value >= 0 then
hourly_forecast_graph:add_value(value)
hourly_forecast_negative_graph:add_value(0)
else
hourly_forecast_graph:add_value(0)
hourly_forecast_negative_graph:add_value(math.abs(value))
end
end
local count = #self
for i = 0, count do self[i]=nil end
-- all temperatures are positive
if min_temp > 0 then
table.insert(self, wibox.widget{
{
hourly_forecast_graph,
reflection = {horizontal = true},
widget = wibox.container.mirror
},
{
temp_below,
valign = 'bottom',
widget = wibox.container.place
},
id = 'graph',
layout = wibox.layout.stack
})
table.insert(self, hours_below)
-- all temperatures are negative
elseif max_temp < 0 then
table.insert(self, hours_below)
table.insert(self, wibox.widget{
{
hourly_forecast_negative_graph,
reflection = {horizontal = true, vertical = true},
widget = wibox.container.mirror
},
{
temp_below,
valign = 'top',
widget = wibox.container.place
},
id = 'graph',
layout = wibox.layout.stack
})
-- there are both negative and positive temperatures
else
table.insert(self, wibox.widget{
{
hourly_forecast_graph,
reflection = {horizontal = true},
widget = wibox.container.mirror
},
{
temp_below,
valign = 'bottom',
widget = wibox.container.place
},
id = 'graph',
layout = wibox.layout.stack
})
table.insert(self, wibox.widget{
{
hourly_forecast_negative_graph,
reflection = {horizontal = true, vertical = true},
widget = wibox.container.mirror
},
{
hours_below,
valign = 'top',
widget = wibox.container.place
},
id = 'graph',
layout = wibox.layout.stack
})
end
end
}
local function update_widget(widget, stdout, stderr)
if stderr ~= '' then
if not warning_shown then
if (stderr ~= 'curl: (52) Empty reply from server'
and stderr ~= 'curl: (28) Failed to connect to api.openweathermap.org port 443: Connection timed out'
and stderr:find('^curl: %(18%) transfer closed with %d+ bytes remaining to read$') ~= nil
) then
show_warning(stderr)
end
warning_shown = true
widget:is_ok(false)
tooltip:add_to_object(widget)
widget:connect_signal('mouse::enter', function() tooltip.text = stderr end)
end
return
end
warning_shown = false
tooltip:remove_from_object(widget)
widget:is_ok(true)
local result = json.decode(stdout)
widget:set_image(ICONS_DIR .. icon_map[result.current.weather[1].icon] .. icons_extension)
widget:set_text(gen_temperature_str(result.current.temp, '%.0f', both_units_widget, units))
current_weather_widget:update(result.current)
local final_widget = {
current_weather_widget,
spacing = 16,
layout = wibox.layout.fixed.vertical
}
if show_hourly_forecast then
hourly_forecast_widget:update(result.hourly)
table.insert(final_widget, hourly_forecast_widget)
end
if show_daily_forecast then
daily_forecast_widget:update(result.daily, result.timezone_offset)
table.insert(final_widget, daily_forecast_widget)
end
weather_popup:setup({
{
final_widget,
margins = 10,
widget = wibox.container.margin
},
bg = beautiful.bg_normal,
widget = wibox.container.background
})
end
weather_widget:buttons(gears.table.join(awful.button({}, 1, function()
if weather_popup.visible then
weather_widget:set_bg('#00000000')
weather_popup.visible = not weather_popup.visible
else
weather_widget:set_bg(beautiful.bg_focus)
weather_popup:move_next_to(mouse.current_widget_geometry)
end
end)))
watch(
string.format(GET_FORECAST_CMD, owm_one_cal_api),
timeout, -- API limit is 1k req/day; day has 1440 min; every 2 min is good
update_widget, weather_widget
)
return weather_widget
end
return setmetatable(weather_widget, {__call = function(_, ...) return worker(...) end})