Arch-Setup/.config/awesome/module/dynamic-wallpaper.lua
2021-11-29 19:57:00 +11:00

365 lines
11 KiB
Lua
Executable File

----------------------------------------------------------------------------
--- Wallpaper changer module
--
-- @author Gerome Matilla <gerome.matilla07@gmail.com>
-- @copyright 2019 Gerome Matilla
-- @module wallchange
--
--- Nevermind this. Do what you want.
----------------------------------------------------------------------------
-- This module changes wallpaper based on declared time
-- It checks the difference between the current time and the next scheduled time
-- Then convert it to seconds to set it as a timeout value
-- Limitations:
-- Timeout paused when laptop/pc is suspended or in sleep mode, and there's probably some bugs too so whatever
local awful = require('awful')
local gears = require('gears')
local beautiful = require('beautiful')
local filesystem = gears.filesystem
local config = require('configuration.config')
-- ========================================
-- Configuration
-- Change your preference here
-- ========================================
local wall_config = {
-- Wallpaper directory. The default is:
-- local wall_config.wall_dir = os.getenv('HOME') .. 'Pictures/Wallpapers/'
wall_dir = filesystem.get_configuration_dir() .. (config.module.dynamic_wallpaper.wall_dir or 'theme/wallpapers/'),
-- If there's a picture format that awesome accepts and i missed
-- (which i probably did) feel free to add it right here
valid_picture_formats = config.module.dynamic_wallpaper.valid_picture_formats or {"jpg", "png", "jpeg"},
-- Table mapping schedule to wallpaper filename
wallpaper_schedule = config.module.dynamic_wallpaper.wallpaper_schedule or {
['00:00:00'] = 'midnight-wallpaper.jpg',
['06:22:00'] = 'morning-wallpaper.jpg',
['12:00:00'] = 'noon-wallpaper.jpg',
['17:58:00'] = 'night-wallpaper.jpg'
},
-- Don't stretch wallpaper on multihead setups if true
stretch = config.module.dynamic_wallpaper.stretch or false
}
-- ========================================
-- Processes
-- Don't touch it if it's working
-- ========================================
-- Get current time
local current_time = function()
return os.date('%H:%M:%S')
end
-- Countdown variable
-- In seconds
local the_countdown = nil
-- Parse seconds to HH:MM:SS
local function parse_to_time(seconds)
-- DST ruined me :(
--return os.date("%H:%M:%S", seconds)
local function format(str)
while #str < 2 do
str = '0' .. str
end
return str
end
local function convert(num)
return format(tostring(num))
end
local hours = convert(math.floor(seconds / 3600))
seconds = seconds - (hours * 3600)
local minutes = convert(math.floor(seconds / 60))
seconds = seconds - (minutes * 60)
local seconds = convert(math.floor(seconds))
return (hours .. ':' .. minutes .. ':' .. seconds)
end
-- Parse HH:MM:SS to seconds
local parse_to_seconds = function(time)
-- Convert HH in HH:MM:SS
local hour_sec = tonumber(string.sub(time, 1, 2)) * 3600
-- Convert MM in HH:MM:SS
local min_sec = tonumber(string.sub(time, 4, 5)) * 60
-- Get SS in HH:MM:SS
local get_sec = tonumber(string.sub(time, 7, 8))
-- Return computed seconds
return (hour_sec + min_sec + get_sec)
end
-- Get time difference
local time_diff = function(future, past)
local diff = parse_to_seconds(future) - parse_to_seconds(past)
if diff < 0 then
diff = diff + (24 * 3600) --If time difference is negative, the future is meant for tomorrow
end
return diff
end
-- Returns a table containing all file paths in a directory
local function get_dir_contents(dir)
-- Command to give list of files in directory
local dir_explore = 'find ' .. dir .. ' -printf "%f\\n"'
local lines = io.popen(dir_explore):lines() --Done synchronously because we literally can't continue without files
local files = {}
for line in lines do
table.insert(files, line)
end
return files
end
-- Returns a table of all the files that were one of the valid file formats
local function filter_files_by_format(files, valid_file_formats)
local valid_files = {}
for _, file in ipairs(files) do
for _, format in ipairs(valid_file_formats) do
if string.match(file, ".+%." .. format) ~= nil then
table.insert(valid_files, file)
break --No need to check other formats
end
end
end
return valid_files
end
-- Returns a table of files that contained any of the keywords, in the same order as the words themselves
local function find_files_containing_keywords(files, keywords)
local found_files = {}
for _, word in ipairs(keywords) do --Preserves keyword order inherently, conveniently
for _, file in ipairs(files) do
-- Check if file is word, contains word at beginning or contains word between 2 non-alphanumeric characters
if file == word or string.find(file, "^" .. word .. "[^%a]") or string.find(file, "[^%a]" .. word .. "[^%a]") then
found_files[word] = file
break --Only return 1 file per word
end
end
end
return found_files
end
-- Turn an ordered list of files into a scheduled list of files
local function auto_schedule(wall_list)
local sched = {}
for index, file in ipairs(wall_list) do
local auto_time = parse_to_time(parse_to_seconds("24:00:00") * (index - 1) / #wall_list)
sched[auto_time] = file
end
return sched
end
-- Reformat whatever schedule was specified into an actual schedule
if #wall_config.wallpaper_schedule == 0 then
local count = 0
-- Determine if empty or if manual schedule
for k, v in pairs(wall_config.wallpaper_schedule) do
count = count + 1
end
if count == 0 then --Schedule is actually empty
-- Get all pictures
local pictures = filter_files_by_format(get_dir_contents(wall_config.wall_dir), wall_config.valid_picture_formats)
--Sort pictures as sanely as possible
local function order_pictures(a, b) --Attempts to mimic default sort but numbers aren't compared as strings
if tonumber(a) ~= nil and tonumber(b) ~= nil then
return tonumber(a) < tonumber(b)
else
return a < b
end
end
table.sort(pictures, order_pictures)
wall_config.wallpaper_schedule = auto_schedule(pictures)
else --Schedule is manually timed
-- Get times as list
local ordered_times = {}
for time, _ in pairs(wall_config.wallpaper_schedule) do
table.insert(ordered_times, time)
end
-- Sort times using seconds as comparison
local function order_time_asc(a, b)
return parse_to_seconds(a) < parse_to_seconds(b)
end
table.sort(ordered_times, order_time_asc)
-- Get ordered list of keywords from ordered times
local keywords = {}
for index, time in ipairs(ordered_times) do
keywords[index] = wall_config.wallpaper_schedule[time]
end
-- Get any pictures that match keywords
local pictures = filter_files_by_format(get_dir_contents(wall_config.wall_dir), wall_config.valid_picture_formats)
pictures = find_files_containing_keywords(pictures, keywords)
-- Replace keywords with files
for index, time in ipairs(ordered_times) do
local word = wall_config.wallpaper_schedule[time]
if pictures[word] ~= nil then
wall_config.wallpaper_schedule[time] = pictures[word]
else --To avoid crashes, we'll remove entries with invalid keywords
wall_config.wallpaper_schedule[time] = nil
end
end
end
else --Schedule is list of keywords
local keywords = wall_config.wallpaper_schedule
-- Get any pictures that match keywords
local pictures = filter_files_by_format(get_dir_contents(wall_config.wall_dir), wall_config.valid_picture_formats)
pictures = find_files_containing_keywords(pictures, keywords)
-- Order files by keyword (if a file was found for the keyword)
local ordered_pictures = {}
for _, word in ipairs(keywords) do
local file = pictures[word]
if file ~= nil then
table.insert(ordered_pictures, file)
end
end
wall_config.wallpaper_schedule = auto_schedule(ordered_pictures)
end
-- Set wallpaper
local set_wallpaper = function(path)
if wall_config.stretch then
for s in screen do
-- Update wallpaper based on the data in the array
gears.wallpaper.maximized (path, s)
end
else
-- Update wallpaper based on the data in the array
gears.wallpaper.maximized (path)
end
end
-- Update wallpaper (used by the manage_timer function)
-- I think the gears.wallpaper.maximized is too fast or being ran asynchronously
-- So the wallpaper is not being updated on awesome (re)start without this timer
-- We need some delay.
-- Hey it's working, so whatever
local update_wallpaper = function(wall_name)
local wall_dir = wall_config.wall_dir .. wall_name
set_wallpaper(wall_dir)
-- Overwrite the default wallpaper
-- This is important in case we add an extra monitor
beautiful.wallpaper = wall_dir
end
-- Updates variables
local manage_timer = function()
-- Get current time
local time_now = parse_to_seconds(current_time())
local previous_time = '' --Scheduled time that should activate now
local next_time = '' --Time that should activate next
local first_time = '24:00:00' --First scheduled time registered (to be found)
local last_time = '00:00:00' --Last scheduled time registered (to be found)
-- Find previous_time
for time, wallpaper in pairs(wall_config.wallpaper_schedule) do
local parsed_time = parse_to_seconds(time)
if previous_time == '' or parsed_time > parse_to_seconds(previous_time) then
if parsed_time <= time_now then
previous_time = time
end
end
if parsed_time > parse_to_seconds(last_time) then
last_time = time
end
end
-- Previous time being blank = no scheduled time today. Therefore
-- the last time was yesterday's latest time
if previous_time == '' then
previous_time = last_time
end
--Find next_time
for time, wallpaper in pairs(wall_config.wallpaper_schedule) do
local parsed_time = parse_to_seconds(time)
if next_time == '' or parsed_time < parse_to_seconds(next_time) then
if parsed_time > time_now then
next_time = time
end
end
if parsed_time < parse_to_seconds(first_time) then
first_time = time
end
end
-- Next time being blank means that there is no scheduled times left for
-- the current day. So next scheduled time is tomorrow's first time
if next_time == '' then
next_time = first_time
end
-- Update Wallpaper
update_wallpaper(wall_config.wallpaper_schedule[previous_time])
-- Get the time difference to set as timeout for the wall_updater timer below
the_countdown = time_diff(next_time, current_time())
end
-- Update values at startup
manage_timer()
local wall_updater = gears.timer {
-- The timeout is the difference of current time and the scheduled time we set above.
timeout = the_countdown,
autostart = true,
call_now = true,
callback = function()
-- Emit signal to update wallpaper
awesome.emit_signal('module::change_wallpaper')
end
}
-- Update wallpaper here and update the timeout for the next schedule
awesome.connect_signal(
'module::change_wallpaper',
function()
--set_wallpaper(wall_dir .. wall_data[2])
-- Update values for the next specified schedule
manage_timer()
-- Update timer timeout for the next specified schedule
wall_updater.timeout = the_countdown
-- Restart timer
wall_updater:again()
end
)