From 39d72e4c0d71ad1f9b405b4b0ee3c8c9d463939a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Mon, 17 Jun 2024 16:56:10 +0200 Subject: [PATCH 1/8] refactor: rewrite weather.lua to use WeatherAPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When I looked into OpenWeatherMap 3.0 API they demanded credit card information even for their free tier. Therefore I decided to move to another provider with a generous tier. In a first step I rewrote the current weather report (i.e. no forecast) to use it. I haven't received any commentary on #442 so I don't know where your mind was going. If OpenWeatherMap phases out its 2.5 API and nobody else intends to use their 3.0 one, I offer to take this implementation instead. Signed-off-by: André Jaenisch --- weather-widget/weather.lua | 373 +++++++++---------------------------- 1 file changed, 85 insertions(+), 288 deletions(-) diff --git a/weather-widget/weather.lua b/weather-widget/weather.lua index 3ec1c3f..3d0a5f8 100644 --- a/weather-widget/weather.lua +++ b/weather-widget/weather.lua @@ -1,9 +1,10 @@ ------------------------------------------------- --- Weather Widget based on the OpenWeatherMap --- https://openweathermap.org/ +-- Weather Widget based on the WeatherAPI +-- https://weatherapi.com/ -- -- @author Pavel Makhov -- @copyright 2020 Pavel Makhov +-- @copyright 2024 André Jaenisch ------------------------------------------------- local awful = require("awful") local watch = require("awful.widget.watch") @@ -26,6 +27,10 @@ end local LANG = gears.filesystem.file_readable(WIDGET_DIR .. "/" .. "locale/" .. SYS_LANG .. ".lua") and SYS_LANG or "en" local LCLE = require("awesome-wm-widgets.weather-widget.locale." .. LANG) +-- WeatherAPI supports only these according to https://www.weatherapi.com/docs/ +-- ar, bn, bg, zh, zh_tw, cs, da, nl, fi, fr, de, el, hi, hu, it, ja, jv, ko, +-- zh_cmn, mr, pl, pt, pa, ro, ru, sr, si, sk, es, sv, ta, te, tr, uk, ur, vi, +-- zh_wuu, zh_hsn, zh_yue, zu local function show_warning(message) @@ -59,36 +64,59 @@ local weather_popup = awful.popup { widget = {} } ---- Maps openWeatherMap icon name to file name w/o extension +--- Maps WeatherAPI condition code to file name w/o extension +--- See https://www.weatherapi.com/docs/#weather-icons 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" + [1000] = "clear-sky", + [1003] = "few-clouds", + [1006] = "scattered-clouds", + [1009] = "scattered-clouds", + [1030] = "mist", + [1063] = "rain", + [1066] = "snow", + [1069] = "rain", + [1072] = "snow", + [1087] = "thunderstorm", + [1114] = "snow", + [1117] = "snow", + [1135] = "mist", + [1147] = "mist", + [1150] = "snow", + [1153] = "snow", + [1168] = "snow", + [1171] = "snow", + [1180] = "rain", + [1183] = "rain", + [1186] = "rain", + [1189] = "rain", + [1192] = "rain", + [1195] = "rain", + [1198] = "rain", + [1201] = "rain", + [1204] = "snow", + [1207] = "snow", + [1210] = "snow", + [1213] = "snow", + [1216] = "snow", + [1219] = "snow", + [1222] = "snow", + [1225] = "snow", + [1237] = "snow", + [1240] = "rain", + [1243] = "rain", + [1246] = "rain", + [1249] = "snow", + [1252] = "snow", + [1255] = "snow", + [1258] = "snow", + [1261] = "snow", + [1264] = "snow", + [1273] = "thunderstorm", + [1276] = "thunderstorm", + [1279] = "thunderstorm", + [1282] = "thunderstorm" } ---- 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 = LCLE.directions - 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 @@ -117,11 +145,11 @@ 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' + 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 '' .. uvi .. '' @@ -152,13 +180,10 @@ local function worker(user_args) 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 '') .. - '&lang=' .. LANG) + local weather_api = + ('https://api.weatherapi.com/v1/current.json' .. + '?q=' .. coordinates[1] .. ',' .. coordinates[2] .. '&key=' .. api_key .. + '&units=' .. units .. '&lang=' .. LANG) weather_widget = wibox.widget { { @@ -267,233 +292,15 @@ local function worker(user_args) 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)) + ICONS_DIR .. icon_map[weather.condition.code] .. icons_extension) + self:get_children_by_id('temp')[1]:set_text(gen_temperature_str(weather.temp_c, '%.0f', false, units)) self:get_children_by_id('feels_like_temp')[1]:set_text( - LCLE.feels_like .. gen_temperature_str(weather.feels_like, '%.0f', false, units)) - self:get_children_by_id('description')[1]:set_text(weather.weather[1].description) + LCLE.feels_like .. gen_temperature_str(weather.feelslike_c, '%.0f', false, units)) + self:get_children_by_id('description')[1]:set_text(weather.condition.text) self:get_children_by_id('wind')[1]:set_markup( - LCLE.wind .. '' .. weather.wind_speed .. 'm/s (' .. to_direction(weather.wind_deg) .. ')') + LCLE.wind .. '' .. weather.wind_kph .. 'km/h (' .. weather.wind_dir .. ')') self:get_children_by_id('humidity')[1]:set_markup(LCLE.humidity .. '' .. weather.humidity .. '%') - self:get_children_by_id('uv')[1]:set_markup(LCLE.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 = '' - .. string.format('%.0f', hour.temp) .. '°' .. '', - 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 + self:get_children_by_id('uv')[1]:set_markup(LCLE.uv .. uvi_index_color(weather.uv)) end } @@ -501,7 +308,7 @@ local function worker(user_args) 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 ~= 'curl: (28) Failed to connect to api.weatherapi.com port 443: Connection timed out' and stderr:find('^curl: %(18%) transfer closed with %d+ bytes remaining to read$') ~= nil ) then show_warning(stderr) @@ -520,9 +327,9 @@ local function worker(user_args) 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)) + widget:set_image(ICONS_DIR .. icon_map[result.current.condition.code] .. icons_extension) + -- TODO: if units isn't "metric", read temp_f instead + widget:set_text(gen_temperature_str(result.current.temp_c, '%.0f', both_units_widget, units)) current_weather_widget:update(result.current) @@ -532,16 +339,6 @@ local function worker(user_args) 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, @@ -554,17 +351,17 @@ local function worker(user_args) 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))) + 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), + string.format(GET_FORECAST_CMD, weather_api), timeout, -- API limit is 1k req/day; day has 1440 min; every 2 min is good update_widget, weather_widget ) From c734c05614edd6dbb2dd935475cc84947c26282a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Tue, 18 Jun 2024 18:16:54 +0200 Subject: [PATCH 2/8] feat: safeguard against invalid JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It appears that OpenWeatherMap sometimes sent out bad JSON. Learn from the lesson and do better in this PR. Signed-off-by: André Jaenisch --- weather-widget/weather.lua | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/weather-widget/weather.lua b/weather-widget/weather.lua index 3d0a5f8..24288e4 100644 --- a/weather-widget/weather.lua +++ b/weather-widget/weather.lua @@ -180,7 +180,7 @@ local function worker(user_args) local timeout = args.timeout or 120 local ICONS_DIR = WIDGET_DIR .. '/icons/' .. icon_pack_name .. '/' - local weather_api = + local weather_api = ('https://api.weatherapi.com/v1/current.json' .. '?q=' .. coordinates[1] .. ',' .. coordinates[2] .. '&key=' .. api_key .. '&units=' .. units .. '&lang=' .. LANG) @@ -322,13 +322,24 @@ local function worker(user_args) return end + if string.match(stdout, '<') ~= nil then + if not warning_shown then + warning_shown = true + widget:is_ok(false) + tooltip:add_to_object(widget) + + widget:connect_signal('mouse::enter', function() tooltip.text = stdout 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.condition.code] .. icons_extension) - -- TODO: if units isn't "metric", read temp_f instead + -- TODO: if units isn't "metric", read temp_f instead widget:set_text(gen_temperature_str(result.current.temp_c, '%.0f', both_units_widget, units)) current_weather_widget:update(result.current) From 11c62fb80bd9bda1fe7ba52f20aec90e809aa7bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Tue, 18 Jun 2024 18:19:23 +0200 Subject: [PATCH 3/8] fix: drop unused variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let's make Luacheck happy! Signed-off-by: André Jaenisch --- weather-widget/weather.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/weather-widget/weather.lua b/weather-widget/weather.lua index 24288e4..eb7a7cc 100644 --- a/weather-widget/weather.lua +++ b/weather-widget/weather.lua @@ -171,10 +171,7 @@ local function worker(user_args) 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 From ebcf76ae17639432ceab7d3334286717fac7a3d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Tue, 18 Jun 2024 18:21:34 +0200 Subject: [PATCH 4/8] fix: missed a blank line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Jaenisch --- weather-widget/weather.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weather-widget/weather.lua b/weather-widget/weather.lua index eb7a7cc..c53e484 100644 --- a/weather-widget/weather.lua +++ b/weather-widget/weather.lua @@ -324,7 +324,7 @@ local function worker(user_args) warning_shown = true widget:is_ok(false) tooltip:add_to_object(widget) - + widget:connect_signal('mouse::enter', function() tooltip.text = stdout end) end return From 9abce7fa9a0c708e44017261a8ac642d8bccc268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Wed, 19 Jun 2024 08:17:19 +0200 Subject: [PATCH 5/8] chore: remove dead code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Luacheck fails over this unused code. Streetturtle gave me green light to remove it as part of this PR. Signed-off-by: André Jaenisch --- apt-widget/apt-widget.lua | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apt-widget/apt-widget.lua b/apt-widget/apt-widget.lua index 0ad6639..f560cb6 100644 --- a/apt-widget/apt-widget.lua +++ b/apt-widget/apt-widget.lua @@ -63,14 +63,6 @@ local apt_widget = wibox.widget({ end, }) -local apt_widget_button = wibox.widget({ - { - apt_widget, - widget = wibox.container.margin, - }, - widget = clickable_container, -- luacheck: ignore (todo fix) -}) - --- Parses the line and creates the package table out of it --- yaru-theme-sound/focal-updates,focal-updates 20.04.10.1 all [upgradable from: 20.04.8] local parse_package = function(line) From f0cb75f40f3898140180fbeaeca3ee28e4ee07a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Tue, 25 Jun 2024 08:24:07 +0200 Subject: [PATCH 6/8] refactor: extract WeatherAPI widget into its own MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was done to allow for choice. In the first step I copied over the existing code into a new folder and accompanied it with icons and locales. There is no indicator of night in the JSON responses, which made me delete the respective icons. I copied and updated the README to fit the current implementation. I will need to add screenshots before I can ask for another round of review. Signed-off-by: André Jaenisch --- weather-api-widget/README.md | 110 +++++ .../icons/VitalyGorbachev/broken-clouds.svg | 1 + .../icons/VitalyGorbachev/clear-sky.svg | 1 + .../icons/VitalyGorbachev/few-clouds.svg | 1 + .../icons/VitalyGorbachev/mist.svg | 1 + .../icons/VitalyGorbachev/rain.svg | 1 + .../VitalyGorbachev/scattered-clouds.svg | 1 + .../icons/VitalyGorbachev/shower-rain.svg | 1 + .../icons/VitalyGorbachev/snow.svg | 1 + .../icons/VitalyGorbachev/thunderstorm.svg | 1 + .../broken-clouds.png | Bin 0 -> 1600 bytes .../weather-underground-icons/clear-sky.png | Bin 0 -> 1029 bytes .../weather-underground-icons/few-clouds.png | Bin 0 -> 1634 bytes .../icons/weather-underground-icons/mist.png | Bin 0 -> 1042 bytes .../icons/weather-underground-icons/rain.png | Bin 0 -> 1728 bytes .../scattered-clouds.png | Bin 0 -> 1044 bytes .../weather-underground-icons/shower-rain.png | Bin 0 -> 1728 bytes .../icons/weather-underground-icons/snow.png | Bin 0 -> 1191 bytes .../thunderstorm.png | Bin 0 -> 2052 bytes weather-api-widget/locale/de.lua | 13 + weather-api-widget/locale/en.lua | 14 + weather-api-widget/locale/fr.lua | 14 + weather-api-widget/locale/pt.lua | 14 + weather-api-widget/weather.lua | 380 ++++++++++++++++++ 24 files changed, 554 insertions(+) create mode 100644 weather-api-widget/README.md create mode 100644 weather-api-widget/icons/VitalyGorbachev/broken-clouds.svg create mode 100644 weather-api-widget/icons/VitalyGorbachev/clear-sky.svg create mode 100644 weather-api-widget/icons/VitalyGorbachev/few-clouds.svg create mode 100644 weather-api-widget/icons/VitalyGorbachev/mist.svg create mode 100644 weather-api-widget/icons/VitalyGorbachev/rain.svg create mode 100644 weather-api-widget/icons/VitalyGorbachev/scattered-clouds.svg create mode 100644 weather-api-widget/icons/VitalyGorbachev/shower-rain.svg create mode 100644 weather-api-widget/icons/VitalyGorbachev/snow.svg create mode 100644 weather-api-widget/icons/VitalyGorbachev/thunderstorm.svg create mode 100755 weather-api-widget/icons/weather-underground-icons/broken-clouds.png create mode 100755 weather-api-widget/icons/weather-underground-icons/clear-sky.png create mode 100755 weather-api-widget/icons/weather-underground-icons/few-clouds.png create mode 100755 weather-api-widget/icons/weather-underground-icons/mist.png create mode 100755 weather-api-widget/icons/weather-underground-icons/rain.png create mode 100755 weather-api-widget/icons/weather-underground-icons/scattered-clouds.png create mode 100755 weather-api-widget/icons/weather-underground-icons/shower-rain.png create mode 100755 weather-api-widget/icons/weather-underground-icons/snow.png create mode 100755 weather-api-widget/icons/weather-underground-icons/thunderstorm.png create mode 100644 weather-api-widget/locale/de.lua create mode 100644 weather-api-widget/locale/en.lua create mode 100644 weather-api-widget/locale/fr.lua create mode 100644 weather-api-widget/locale/pt.lua create mode 100644 weather-api-widget/weather.lua diff --git a/weather-api-widget/README.md b/weather-api-widget/README.md new file mode 100644 index 0000000..bb26f77 --- /dev/null +++ b/weather-api-widget/README.md @@ -0,0 +1,110 @@ +# WeatherAPI widget + +The widget consists of one section: +- current weather, including humidity, wind speed, UV index + +## Customization + +It is possible to customize widget by providing a table with all or some of the +following config parameters: + +| Name | Default | Description | +|---|---|---| +| coordinates | Required | Table with two elements: latitude and longitude, e.g. `{46.204400, 6.143200}` | +| api_key | Required | [Follow the documentation](https://www.weatherapi.com/docs/) | +| font_name | `beautiful.font:gsub("%s%d+$", "")` | **Name** of the font to use e.g. 'Play' | +| units | `metric` | `metric` for celsius, `imperial` for fahrenheit | +| icon_pack_name | `weather-underground-icons` | Name of the icon pack, could be `weather-underground-icon` or `VitalyGorbachev` or create your own, more details below | +| icons_extension | `.png` | File extension of icons in the pack | +| timeout | 120 | How often in seconds the widget refreshes | + +### Icons: + +The widget comes with two predefined icon packs: + + - [weather-underground-icons](https://github.com/manifestinteractive/weather-underground-icons) + - [VitalyGorbachev](https://www.flaticon.com/authors/vitaly-gorbachev) + +To add your custom icons, create a folder with the pack name under `/icons` and +use the folder name in widget's config. There should be 18 icons, preferably +128x128 minimum. Icons should also respect the naming convention, please check +widget's source. + +### Examples: + +#### Custom font, icons + +```lua +weather_api_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, + units = 'imperial', + font_name = 'Carter One', + icons = 'VitalyGorbachev', + icons_extension = '.svg', +}), +``` + +#### Only current weather + +```lua +weather_api_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, +}), +``` + +## Installation + +1. Download json parser for lua from + [github.com/rxi/json.lua](https://github.com/rxi/json.lua) and place it + under **~/.config/awesome/** + (don't forget to star a repo ): + + ```bash + wget -P ~/.config/awesome/ https://raw.githubusercontent.com/rxi/json.lua/master/json.lua + ``` + +1. Clone this repo under **~/.config/awesome/**: + + ```bash + git clone https://github.com/streetturtle/awesome-wm-widgets.git ~/.config/awesome/ + ``` + +1. [Get Weather API key](https://www.weatherapi.com/docs/). + +1. Require weather widget at the beginning of **rc.lua**: + + ```lua + local weather_api_widget = require("awesome-wm-widgets.weather-api-widget.weather") + ``` + +1. Add widget to the tasklist: + + ```lua + s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + --default + weather_api_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, + }), + , + --customized + weather_api_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, + units = 'imperial', + font_name = 'Carter One', + icons = 'VitalyGorbachev', + icons_extension = '.svg', + }), + ... + ``` + +## How it works + +The widget calls the API repeatedly in the specified intervals. The JSON +response is parsed and interpreted to build the popup. diff --git a/weather-api-widget/icons/VitalyGorbachev/broken-clouds.svg b/weather-api-widget/icons/VitalyGorbachev/broken-clouds.svg new file mode 100644 index 0000000..d42ea59 --- /dev/null +++ b/weather-api-widget/icons/VitalyGorbachev/broken-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/weather-api-widget/icons/VitalyGorbachev/clear-sky.svg b/weather-api-widget/icons/VitalyGorbachev/clear-sky.svg new file mode 100644 index 0000000..dc82163 --- /dev/null +++ b/weather-api-widget/icons/VitalyGorbachev/clear-sky.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/weather-api-widget/icons/VitalyGorbachev/few-clouds.svg b/weather-api-widget/icons/VitalyGorbachev/few-clouds.svg new file mode 100644 index 0000000..d42ea59 --- /dev/null +++ b/weather-api-widget/icons/VitalyGorbachev/few-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/weather-api-widget/icons/VitalyGorbachev/mist.svg b/weather-api-widget/icons/VitalyGorbachev/mist.svg new file mode 100644 index 0000000..770f8d7 --- /dev/null +++ b/weather-api-widget/icons/VitalyGorbachev/mist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/weather-api-widget/icons/VitalyGorbachev/rain.svg b/weather-api-widget/icons/VitalyGorbachev/rain.svg new file mode 100644 index 0000000..11ecf00 --- /dev/null +++ b/weather-api-widget/icons/VitalyGorbachev/rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/weather-api-widget/icons/VitalyGorbachev/scattered-clouds.svg b/weather-api-widget/icons/VitalyGorbachev/scattered-clouds.svg new file mode 100644 index 0000000..d42ea59 --- /dev/null +++ b/weather-api-widget/icons/VitalyGorbachev/scattered-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/weather-api-widget/icons/VitalyGorbachev/shower-rain.svg b/weather-api-widget/icons/VitalyGorbachev/shower-rain.svg new file mode 100644 index 0000000..4d1897c --- /dev/null +++ b/weather-api-widget/icons/VitalyGorbachev/shower-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/weather-api-widget/icons/VitalyGorbachev/snow.svg b/weather-api-widget/icons/VitalyGorbachev/snow.svg new file mode 100644 index 0000000..e2ea140 --- /dev/null +++ b/weather-api-widget/icons/VitalyGorbachev/snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/weather-api-widget/icons/VitalyGorbachev/thunderstorm.svg b/weather-api-widget/icons/VitalyGorbachev/thunderstorm.svg new file mode 100644 index 0000000..44a733c --- /dev/null +++ b/weather-api-widget/icons/VitalyGorbachev/thunderstorm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/weather-api-widget/icons/weather-underground-icons/broken-clouds.png b/weather-api-widget/icons/weather-underground-icons/broken-clouds.png new file mode 100755 index 0000000000000000000000000000000000000000..5967d929720110d12e96ae2117b913876bd9f837 GIT binary patch literal 1600 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD~={fl)ueC&U%V{f_~@Eti3a!5Kg< zg#B%~G!h%63?hQ59$5sg3uXvJqs-qo7l9(k2w4}Dy-Wh40LVqw4&lP3Ad1mQsQP8n z5M|$%Nx@Bkiokq|tPw5+V@Sd5l45vcqX7)Eq>>=NV1|c$w--N~J2m+CsjbVaf-TH- z1z116yq)1-Y$W>S!~LB*RyWq?$NM^1b2I(@_4VA5{d+d_wYQ||sLQ;4^YreCy;Y?p zd9jhM8p`sLS5_~Y*;SDk>8m7v=Jdh!>t;l`&YuuyeQt93Dh39YI8PVHkcwMxu4m^9 zIf}47FrN~2^wK0=SJ&JPtlZPOU7w061xTB=L@)6D6OjM;)BpPB-8>r(oKsVKqjT}i z=gP^S-}#)IDapdr=%66*!(MjFhTzOiJ+iAVnH+i-lA)DU-j|V;n4Z!g@-Ir@K|KG2 zl}U$ZESr>Mb@y=fc9r&s@IW?=32zP*^UgZALCyVg`I^(0OE}L@>|(i_^w}eHeqlx4 z6ORQU4}5s;SqG>ET;JcYMAcF1os!h++ngIp%vUZsF3jw01>`8Gp04!z*es=Db<4@Q zX`9ac2dg~9r_K6P-`mF~9Km+Sq^XA?-bB+hcx6~~kmSG4)2}4YJh@aE<@LdP;xxmY z+$gpW?(JfSD{WJG_APKNjhJ-dj2zqcSEoJM>=vx#U(RyU;>+#@C%;Zja@cKmvLm~= zY(ebB($)1pjC((=dpj+1jqlonJLiA6ccpZBayP@4%o~&LmCS0$Zcu7a$>pqAU&OZc z%OjfwYkB3rdVEXh61BMAK3_^jdxl}poRbafdG5A5zIb`I@`u)+hicMIUY=%+YR4Z6 zC*J0l*Iv$$eL?w`Q%+T7uj&33`HXg)Th;rt1xtQx(!cZOs^Z?x3zJ&dt|uK`Jo&Ql zRVAL~x8z*fzrAz&h5vYb`_P? zy?Co6b**IM-hjP&-1~$tWv{Rix%B($r^2|fjlJjBe>-yKxW*#3Z6dH*tQc00{HcDm?xU)5Ab(=3zf_yw|LXZu{vDfD*Ryr*)16jyg-gzUUvvM;lNOS( zyI%`zKCWT1Jy65+$wh4!ABMFL65W<^F0`KPP$V1trFELkt6%Dl(oU-PS--^p6)Vs` z%M|@->#A^$a0QcPivPNqw%&Rqc=M>~yekTy&rEh>y!oc`QtfT0XXe69T`>y^mZGCaw7h?P;(aL6b!A$CV4!or zXP(3j3yOLRa{SKC4PMZFVb1mkvrdUE{JJ8>GVYN}MTnkEx_oL&`Ri{f)v~&!E!P)a zD%ySM*blb6vqwuZ$`ow0&U*;Hw##bFPhgvosn#T?@a{)u9o!@%Nb3Acbc;` zm$W?yvyoD`+g>ZrUC$)?y{({aLskgiJK0EH$IumPjQ0o^H#E$;^6i}Y@^2zPep;UU zW%?+&O<<9zaMQ~d6;g-KuRlA>O=k{2f48{P;ztc(-UjoyoFoE6*}f{xGGr~@*nHyg t97U6;39GN2{$iNra^h*x&KSM3@ji?j_A{Ech5*Yo22WQ%mvv4FO#s%IT-5*o literal 0 HcmV?d00001 diff --git a/weather-api-widget/icons/weather-underground-icons/clear-sky.png b/weather-api-widget/icons/weather-underground-icons/clear-sky.png new file mode 100755 index 0000000000000000000000000000000000000000..acf8e5cd108a3d4beecfbd8415111d6594d77753 GIT binary patch literal 1029 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&O%Cu0aRt)fmdgw()cGpW4CpL_ zk|4iehQe}*byK3X9`E>Y|4?_3i_rJiUUo(*vpTYqEp(?(Xse44=V1AJ^XkQ8E1FL) zmgD_5_aD$}Odma6978H@y}kLmXqABo>jPDdd#_@zRIM+M-un9Q{C86xC{}PWYFYZE zfBO6S%1j-nO*a`O+7iL&<8R&^k<9DLx*{)5=o^c)&lBx(_UBwVlR>S4^FuVVkA0cF zrptDz(>$g2Ri&xsnyWv*dH^c+AH9 zT+`{-!5@~hY}Pdi{r{?T`*Gp9$ET}8+TZsE`dQzZy7kMc&tBFu9!aSF;g>zO+rhr_ zd-gRSpI;fl&*E)gd^@$fNL(?$+LcN6^Zh%=&Yoeks=f5Rk;C!t)fX-cdp&%)j58i< z9B0q^VzPj@)$+`|iEQik{}0~6q<ls<=ESb^IbP&Us9LeXl83B6mxVj)1ir%v^U*&Bl+x<8HZ<7 z(b74RW)5t=b{U-kFPU^wP16)U=T<&!*4$??GrfU7DsuI@5|=gx(Vc8oJSp!(+_G0c z`F*2>e`VgPle--@=&pVeZC!E8Y=`v8(g(6uUoPn^&Dv1-S@m?yDfTwLwN@vQ#Rd0g6;?&R2st|UZo-n# z56$0Mi_E#|XJx$q*}JCRSax=z-+A31)e-3t^}H`vBwB|l&-y(x)c)G6t86)o|2==_`1+JJ({Il?F3;SmGUN0|ZqEyUFMjVY=km45U%2Z(d(kSNgy*6O zlK*20LODO!Sl88D*zC3?JC6VO#fA6XOul*^+`22^hwJ?Jz6`<#SUhT~qvynZxV1u| m&w^X*N0I^>B88+Fp%PgQ*g_Bi5kzLd*>LqBbs&|}5C)hHbqPcSs1kzE6u`Nd zTHsR18X-196eEixYln*;(z$*R7)CtJGo}Ttjtt()ngl4lM18*^e9~ z*gmj7nx7_W7$%~9aMhLO2MaqcG;`cKJnzV5J&`wZr`~=3Yk#QkA$b~!He1%J&S^^mW* zYAYNg`^B&P@oGy>;kn_vA$b1&HS4l}T2xQ_`gUbNgNgLVMPld5-t0NB?j%dl-0;5# z)n5b&=WUnHl6!b-Ub_8q=Q$?Ylf(aAZjHXT^Mc4O=l)B&F;T%$u`)7u)DzyY>wRQ$ zi+gy+>~RiXD05NLArISH57BD{&Uydd?7#BB<#a;7sbpmXch3)xuXkh3U!1O+pQxOC z>-s~^O%LOFl!?kaeW!IeM{1kseD%Za%!`W#5 zC-+~44_7bUcks##EB$jRb!Ts7&Q0*VE8r&ObLixSJ%@F#rav&uWD@rGx#j%fEYpg# z6?Iuok0oQ;C!2p}2<0j^_0c}a;Kp`^CI6OA%9aC8)&lV>X38g=TJ5pf+T)q5*^&(o zYgSI175Rd>{`SWI*S0ho@IAQScd_hk%&`cC4HGygXDts-cU`Sywd!2F#ah0(hi(OZ zsQmvvA;!jabH=&dlMO$7p8x+yzFzrx+j4ou2KTFPdtW54=L_L1)UseGFMcNUtG0qw zu>H;1qth?&{+n5@74IPXo^9Vl1FxqM1^17{IPQ)+DI}X3xJpcS^pln%qC7-uYb1!tjoo-VHCGPG`Q$zocXH<=(R2hQW$?9j4+B ze+qr+NHgY4pQD?ac`&nCTSwV4;@4TV`PZ5Y^IT@@teMGsBJ_CEx9CklQ*GvL%=B0{ zV|(K-7TH}-LYuCe8hRRR-)Fw`bNqx5h4nW7+}0V{a~VFL9$^`t+10UG_W8USN4`D! zz;ONg_w@W)txq#gZR+2C_hQ0+uiM49K9pBw|)J)^ru)I(GFa?>Op`-~GE0>KSj z|0Zm7;=LT)QFY1nPNPzDOzq7VVj-1%iHp*1l`U4&4Vm5gh}&>)(!H3cVd6@anJ*G1 ztx@@6AaXxGHr?g-?mWlE9v$B%Z&(mn9(8(&{}G8^jy@3h7hlhCy7J5EtL{E(zygoK M)78&qol`;+0CaY1iU0rr literal 0 HcmV?d00001 diff --git a/weather-api-widget/icons/weather-underground-icons/mist.png b/weather-api-widget/icons/weather-underground-icons/mist.png new file mode 100755 index 0000000000000000000000000000000000000000..102142a239ae2ff93f0b63119eb555e3e3370082 GIT binary patch literal 1042 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 b{PB#puke(aTQKzi^E-p5tDnm{r-UW|;ps;f literal 0 HcmV?d00001 diff --git a/weather-api-widget/icons/weather-underground-icons/thunderstorm.png b/weather-api-widget/icons/weather-underground-icons/thunderstorm.png new file mode 100755 index 0000000000000000000000000000000000000000..210210435319ffe8cbb502195662b7b152d47839 GIT binary patch literal 2052 zcmbtVc{H1O8%<&2T2l>eDVhkAu~Zb%P6kbvL93x=LTjrema&s)QQKEa zGNq{27)5QdHP))NifYNUOjU`sTJz4Ab8?*i`~A*&?|Yx;-rs$mbKXDR1Q%z#9CR-f z1Omy~|6uD1ylMYFQeprKUNbjAAaIR~le-;IF0XIz?Ch*@v;YJSDz;Nd{!jcS2PlOd z;UJMJ>VGp1uTXH6m9PVFfQUE{BAl|$ zfeVv`l5j(jO>{*#R9K3lgakk#0X!lb(Aiyza)ikM0){?lQ9AJD(x|S^ZXk%W?Vt52 z7JJ=xLJ^q5_cNDCx;;g6zClqJQt*XAZ(j*a&9wzt;(!On=gd5z+K;rv-A+vBDN z2Aa_Am6`E6RhEul93B zeoxOlvgxxi_wmR2l|Hi1zo#R;{DLL%)Z7WRDVxUy{m+S)C-s)<8fzRPDqKJRb_N~Q zj;NCLZXZW>Cg>|YCFJR*#Fq<>4<^CjnZsWqTuTC_nRV+)i>5Nup>%@0V&SPUwd+f+ zI(P-`$2fvI*7=@y3)IUbnR)01GulLbl4LSHUz{(`@z;FY{yv@?|1>042j29RXG)@{ z{`77l#iUD4FWi(U$&nqh$tFVa1A=CZd5ps#Gd5EhZ}^2vWGz>g!{cd$MBf0bgT*LI z=T8EOq1*3Exmo5c<8lggAIfMj=#Mnt#k2W(&8)DUl+Sl`S?Mp>5BJz5sg4v@*w1{w zaCu%mX#bU?sH?b6Wy)_BWgoGQKk2lEtK9R7Znbu&qI&LRdn&sykC}|-yKTtB@YVYk zj4?WNLyE+^agR!R5167q;O6#$v54G9RN-*!oQzg51ck7zY9b$<%{vkqKG3QkC7G^v zkK2a1%?_MoO^I2}M=nNVpei#) zy59cyqxaTUUiV9TTaK4Gov~qq&ll_jAtAMR-Fa#??xR(z5|iX7zM#sA=edqmjZ8!p zsVLL!X5>k_i4oYqnBg??#W%)7_*-wP=W?Wms5Pk??h%`Zd$Su8FuBM%_{e{_GfQw? zS=5lCpFh!zo`#zsY{ENK#CdF0l^e&Xm{zUW?cx`Hn^ECwBQI)+zaGZe?7Ga+UxKnVVa45KoC6o14rk^ebOx4h`Ivib zSZbiRB`mfIkv_1+^+CSDn0nj0!IJx90mER57bDrGyS!)*Lq4fm<10ixIpl4y>QeLH zA;DdKkN`g|X!yZVaq3m*BZ^?Xav}KFG+u-F1FgI+dUN8k{HB;Ii@0FpF2iM$>7f)-qjUEjH&Ap$biY51 z-65F-qu&x7qIq}YwYC~PjaYOF)|$~0HCHEwX6F7W6KSA&>AxpE`TbwY$desYe^GrW zPp*4oa(vgCUyXlPOR{(qTyP^ypYaPEcIk?P=ivmyXVN3#?v72b1@}meG9%`tvrov# z(}!~RtQK&&Z=We)UBJ@yfv;rZDB)sx*B4pE@=l>Gk0^Mhp$A{^k`)hB@(|~i2MY%H zvX%`0UTIe;_C!cE*AA-|I@N{5Xx9ENiI(CUSv@Uj$a>1ZZn!zqetNHl#Q0ru?Jef9?1qE^;|aT}3&xpMx#j8AIVg^*J;JRZClhCZ8Blq1Tp2fNVL~H!E9NW*)2Z zyeLjcl&qmAXR$zjNHQs;qo)}Rtvm1J5$OuCwi{M)Z>XX|XW}&{9a9GNfb1hPEt|77J84i$8r_IU&tT*okX_bTRxZ4+Pz9O8 l&g!=cC;<{?z&X= 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 '' .. uvi .. '' +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(LCLE.parameter_warning .. + (args.coordinates == nil and 'coordinates' or '') .. + (args.api_key == nil and ', api_key ' 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 both_units_widget = args.both_units_widget or false + 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 weather_api = + ('https://api.weatherapi.com/v1/current.json' .. + '?q=' .. coordinates[1] .. ',' .. coordinates[2] .. '&key=' .. api_key .. + '&units=' .. units .. '&lang=' .. LANG) + + 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, + }, + left = 4, + right = 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.condition.code] .. icons_extension) + self:get_children_by_id('temp')[1]:set_text(gen_temperature_str(weather.temp_c, '%.0f', false, units)) + self:get_children_by_id('feels_like_temp')[1]:set_text( + LCLE.feels_like .. gen_temperature_str(weather.feelslike_c, '%.0f', false, units)) + self:get_children_by_id('description')[1]:set_text(weather.condition.text) + self:get_children_by_id('wind')[1]:set_markup( + LCLE.wind .. '' .. weather.wind_kph .. 'km/h (' .. weather.wind_dir .. ')') + self:get_children_by_id('humidity')[1]:set_markup(LCLE.humidity .. '' .. weather.humidity .. '%') + self:get_children_by_id('uv')[1]:set_markup(LCLE.uv .. uvi_index_color(weather.uv)) + 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.weatherapi.com 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 + + if string.match(stdout, '<') ~= nil then + if not warning_shown then + warning_shown = true + widget:is_ok(false) + tooltip:add_to_object(widget) + + widget:connect_signal('mouse::enter', function() tooltip.text = stdout 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.condition.code] .. icons_extension) + -- TODO: if units isn't "metric", read temp_f instead + widget:set_text(gen_temperature_str(result.current.temp_c, '%.0f', both_units_widget, units)) + + current_weather_widget:update(result.current) + + local final_widget = { + current_weather_widget, + spacing = 16, + layout = wibox.layout.fixed.vertical + } + + 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, weather_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}) From df7260503d0cd7bb0e557f8ad111615491d9cda1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Tue, 25 Jun 2024 08:33:30 +0200 Subject: [PATCH 7/8] refactor: restore original implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is going to be marked as deprecated by streetturtle going forward. As of today, I am still able to successfully call the v2.5 API. Signed-off-by: André Jaenisch --- weather-widget/weather.lua | 387 ++++++++++++++++++++++++++++--------- 1 file changed, 291 insertions(+), 96 deletions(-) diff --git a/weather-widget/weather.lua b/weather-widget/weather.lua index c53e484..3ec1c3f 100644 --- a/weather-widget/weather.lua +++ b/weather-widget/weather.lua @@ -1,10 +1,9 @@ ------------------------------------------------- --- Weather Widget based on the WeatherAPI --- https://weatherapi.com/ +-- Weather Widget based on the OpenWeatherMap +-- https://openweathermap.org/ -- -- @author Pavel Makhov -- @copyright 2020 Pavel Makhov --- @copyright 2024 André Jaenisch ------------------------------------------------- local awful = require("awful") local watch = require("awful.widget.watch") @@ -27,10 +26,6 @@ end local LANG = gears.filesystem.file_readable(WIDGET_DIR .. "/" .. "locale/" .. SYS_LANG .. ".lua") and SYS_LANG or "en" local LCLE = require("awesome-wm-widgets.weather-widget.locale." .. LANG) --- WeatherAPI supports only these according to https://www.weatherapi.com/docs/ --- ar, bn, bg, zh, zh_tw, cs, da, nl, fi, fr, de, el, hi, hu, it, ja, jv, ko, --- zh_cmn, mr, pl, pt, pa, ro, ru, sr, si, sk, es, sv, ta, te, tr, uk, ur, vi, --- zh_wuu, zh_hsn, zh_yue, zu local function show_warning(message) @@ -64,59 +59,36 @@ local weather_popup = awful.popup { widget = {} } ---- Maps WeatherAPI condition code to file name w/o extension ---- See https://www.weatherapi.com/docs/#weather-icons +--- Maps openWeatherMap icon name to file name w/o extension local icon_map = { - [1000] = "clear-sky", - [1003] = "few-clouds", - [1006] = "scattered-clouds", - [1009] = "scattered-clouds", - [1030] = "mist", - [1063] = "rain", - [1066] = "snow", - [1069] = "rain", - [1072] = "snow", - [1087] = "thunderstorm", - [1114] = "snow", - [1117] = "snow", - [1135] = "mist", - [1147] = "mist", - [1150] = "snow", - [1153] = "snow", - [1168] = "snow", - [1171] = "snow", - [1180] = "rain", - [1183] = "rain", - [1186] = "rain", - [1189] = "rain", - [1192] = "rain", - [1195] = "rain", - [1198] = "rain", - [1201] = "rain", - [1204] = "snow", - [1207] = "snow", - [1210] = "snow", - [1213] = "snow", - [1216] = "snow", - [1219] = "snow", - [1222] = "snow", - [1225] = "snow", - [1237] = "snow", - [1240] = "rain", - [1243] = "rain", - [1246] = "rain", - [1249] = "snow", - [1252] = "snow", - [1255] = "snow", - [1258] = "snow", - [1261] = "snow", - [1264] = "snow", - [1273] = "thunderstorm", - [1276] = "thunderstorm", - [1279] = "thunderstorm", - [1282] = "thunderstorm" + ["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 = LCLE.directions + 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 @@ -145,11 +117,11 @@ 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' + 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 '' .. uvi .. '' @@ -171,16 +143,22 @@ local function worker(user_args) 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 weather_api = - ('https://api.weatherapi.com/v1/current.json' .. - '?q=' .. coordinates[1] .. ',' .. coordinates[2] .. '&key=' .. api_key .. - '&units=' .. units .. '&lang=' .. LANG) + 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 '') .. + '&lang=' .. LANG) weather_widget = wibox.widget { { @@ -289,15 +267,233 @@ local function worker(user_args) layout = wibox.layout.flex.horizontal, update = function(self, weather) self:get_children_by_id('icon')[1]:set_image( - ICONS_DIR .. icon_map[weather.condition.code] .. icons_extension) - self:get_children_by_id('temp')[1]:set_text(gen_temperature_str(weather.temp_c, '%.0f', false, units)) + 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( - LCLE.feels_like .. gen_temperature_str(weather.feelslike_c, '%.0f', false, units)) - self:get_children_by_id('description')[1]:set_text(weather.condition.text) + LCLE.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( - LCLE.wind .. '' .. weather.wind_kph .. 'km/h (' .. weather.wind_dir .. ')') + LCLE.wind .. '' .. weather.wind_speed .. 'm/s (' .. to_direction(weather.wind_deg) .. ')') self:get_children_by_id('humidity')[1]:set_markup(LCLE.humidity .. '' .. weather.humidity .. '%') - self:get_children_by_id('uv')[1]:set_markup(LCLE.uv .. uvi_index_color(weather.uv)) + self:get_children_by_id('uv')[1]:set_markup(LCLE.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 = '' + .. string.format('%.0f', hour.temp) .. '°' .. '', + 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 } @@ -305,7 +501,7 @@ local function worker(user_args) 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.weatherapi.com port 443: Connection timed out' + 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) @@ -319,25 +515,14 @@ local function worker(user_args) return end - if string.match(stdout, '<') ~= nil then - if not warning_shown then - warning_shown = true - widget:is_ok(false) - tooltip:add_to_object(widget) - - widget:connect_signal('mouse::enter', function() tooltip.text = stdout 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.condition.code] .. icons_extension) - -- TODO: if units isn't "metric", read temp_f instead - widget:set_text(gen_temperature_str(result.current.temp_c, '%.0f', both_units_widget, units)) + + 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) @@ -347,6 +532,16 @@ local function worker(user_args) 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, @@ -359,17 +554,17 @@ local function worker(user_args) 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))) + 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, weather_api), + 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 ) From d9e9ee693879df5dcee50b250fe4287317a9dbf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Tue, 2 Jul 2024 08:37:36 +0200 Subject: [PATCH 8/8] chore: include screenshot of popup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unlike weather widget I only have the current weather in place here. I'm okay with the default font so I have not created another screenshot. Signed-off-by: André Jaenisch --- weather-api-widget/README.md | 4 +++- weather-api-widget/popup.png | Bin 0 -> 4926 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 weather-api-widget/popup.png diff --git a/weather-api-widget/README.md b/weather-api-widget/README.md index bb26f77..4ea4a44 100644 --- a/weather-api-widget/README.md +++ b/weather-api-widget/README.md @@ -1,5 +1,7 @@ # WeatherAPI widget +![Current Weather popup](./popup.png) + The widget consists of one section: - current weather, including humidity, wind speed, UV index @@ -30,7 +32,7 @@ use the folder name in widget's config. There should be 18 icons, preferably 128x128 minimum. Icons should also respect the naming convention, please check widget's source. -### Examples: +### Examples #### Custom font, icons diff --git a/weather-api-widget/popup.png b/weather-api-widget/popup.png new file mode 100644 index 0000000000000000000000000000000000000000..1e668a7a1f195629947bdb65f77ce9fdd25cb94a GIT binary patch literal 4926 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 literal 0 HcmV?d00001