2024-06-03 19:38:03 +00:00
#!/usr/bin/env python3
from sys import exit
from os import chdir , getenv , system
import logging
import tomllib
from datetime import datetime , time
from apscheduler . schedulers . blocking import BlockingScheduler
from apscheduler . triggers . cron import CronTrigger
# setup logging
logger = logging . getLogger ( __name__ )
class ConfigError ( Exception ) :
pass
class _ConfigLib :
def _initialize_config ( self ) - > dict :
chdir ( str ( getenv ( " HOME " ) ) + " /.config/wallman/ " )
with open ( " wallman.toml " , " rb " ) as config_file :
data = tomllib . load ( config_file )
return data
def __init__ ( self ) :
self . config_file : dict = self . _initialize_config ( ) # Full config
# Dictionaries
self . config_general : dict = self . config_file [ " general " ]
self . config_changing_times : dict = self . config_file [ " changing_times " ]
# Values in Dicts
self . config_wallpaper_sets_enabled : bool = self . config_general [ " enable_wallpaper_sets " ]
self . config_used_sets : list = self . config_general [ " used_sets " ]
self . config_wallpapers_per_set : int = self . config_general [ " wallpapers_per_set " ]
self . config_total_changing_times : int = len ( self . config_changing_times )
2024-06-20 15:57:20 +00:00
self . config_log_level : str = self . config_general . get ( " loglevel " , " INFO " ) . upper ( )
2024-06-03 19:38:03 +00:00
try :
2024-06-09 19:25:26 +00:00
self . config_notify : bool = self . config_general [ " notify " ]
2024-06-03 19:38:03 +00:00
except KeyError :
self . config_notify = False
logger . warning ( " ' notify ' is not set in dictionary general in the config file, defaulting to ' false ' . " )
2024-06-09 19:38:08 +00:00
# Setup logging
2024-06-09 19:25:26 +00:00
self . _set_log_level ( )
def _set_log_level ( self ) :
global logging
global logger
chdir ( str ( getenv ( " HOME " ) ) + " /.local/share/wallman/ " )
2024-06-20 15:57:20 +00:00
numeric_level = getattr ( logging , self . config_log_level , logging . INFO )
2024-06-09 19:25:26 +00:00
logger . setLevel ( numeric_level )
logging . basicConfig ( filename = " wallman.log " , encoding = " utf-8 " , level = numeric_level )
2024-06-03 19:38:03 +00:00
def _set_fallback_wallpaper ( self ) :
if self . config_general [ " fallback_wallpaper " ] :
system ( f " feh --bg-fill --no-fehbg { self . config_general [ ' fallback_wallpaper ' ] } " )
logger . info ( " The fallback Wallpaper has been set. " )
else :
logger . critical ( " An Error occured and no fallback wallpaper was provided, exiting... " )
raise ConfigError ( " An error occured and no fallback wallpaper has been set, exiting... " )
class ConfigValidity ( _ConfigLib ) :
def __init__ ( self ) :
super ( ) . __init__ ( )
def _check_fallback_wallpaper ( self ) :
if self . config_general [ " fallback_wallpaper " ] :
logger . debug ( " A fallback wallpaper has been defined. " )
2024-06-06 15:47:44 +00:00
return True
2024-06-03 19:38:03 +00:00
else :
logger . warning ( " No fallback wallpaper has been provided. If the config is written incorrectly, the program will not be able to be executed. " )
2024-06-06 15:47:44 +00:00
return False
2024-06-03 19:38:03 +00:00
2024-06-05 13:07:21 +00:00
def _check_wallpapers_per_set_and_changing_times ( self ) - > bool :
2024-06-03 19:38:03 +00:00
# Check if the amount of wallpapers_per_set and given changing times match
if self . config_total_changing_times == self . config_wallpapers_per_set :
logger . debug ( " The amount of changing times and wallpapers per set is set correctly " )
2024-06-05 13:07:21 +00:00
return True
2024-06-03 19:38:03 +00:00
else :
try :
self . _set_fallback_wallpaper ( )
logger . error ( " The amount of changing_times and the amount of wallpapers_per_set does not much, the fallback wallpaper has been set. " )
print ( " ERROR: The amount of changing_times and the amount of wallpapers_per_set does not much, the fallback wallpaper has been set. " )
2024-06-05 13:07:21 +00:00
return False
2024-06-03 19:38:03 +00:00
except ConfigError :
logger . critical ( " The amount of changing times and the amount of wallpapers per set does not match, exiting... " )
raise ConfigError ( " Please provide an amount of changing_times equal to wallpapers_per_set, exiting... " )
2024-06-05 13:07:21 +00:00
def _check_general_validity ( self ) - > bool :
2024-06-03 19:38:03 +00:00
if len ( self . config_general ) < 3 :
try :
self . _set_fallback_wallpaper ( )
logger . error ( " An insufficient amount of elements has been provided for general, the fallback wallpaper has been set. " )
print ( " ERROR: An insufficient amount of wallpapers has been provided for general, the fallback wallpaper has been set. " )
2024-06-05 13:07:21 +00:00
return False
2024-06-03 19:38:03 +00:00
except ConfigError :
logger . critical ( " An insufficient amount of elements for general has been provided, exiting... " )
raise ConfigError ( " general should have at least 3 elements, exiting... " )
2024-06-05 13:07:21 +00:00
else :
logger . debug ( " A valid amount of options has been provided in general " )
return True
def _check_wallpaper_dicts ( self ) - > bool :
2024-06-03 19:38:03 +00:00
# This block checks if a dictionary for each wallpaper set exists
for wallpaper_set in self . config_used_sets :
if wallpaper_set in self . config_file :
logger . debug ( f " The dictionary { wallpaper_set } has been found in config. " )
2024-06-05 13:07:21 +00:00
return True
2024-06-03 19:38:03 +00:00
else :
try :
self . _set_fallback_wallpaper ( )
logger . error ( f " The dictionary { wallpaper_set } has not been found in the config, the fallback wallpaper has been set. " )
print ( f " ERROR: The dictionary { wallpaper_set } has not been found in the config, the fallback wallpaper has been set. " )
2024-06-05 13:07:21 +00:00
return False
2024-06-03 19:38:03 +00:00
except ConfigError :
logger . critical ( f " No dictionary { wallpaper_set } has been found in the config exiting... " )
raise ConfigError ( f " The dictionary { wallpaper_set } has not been found in the config, exiting... " )
2024-06-05 13:07:21 +00:00
def _check_wallpaper_amount ( self ) - > bool :
2024-06-03 19:38:03 +00:00
# This block checks if if each wallpaper set dictionary provides enough wallpapers to satisfy wallpapers_per_set
for wallpaper_set in self . config_used_sets :
if len ( self . config_file [ wallpaper_set ] ) == self . config_wallpapers_per_set :
logger . debug ( f " Dictionary { wallpaper_set } has sufficient values. " )
2024-06-05 13:07:21 +00:00
return True
2024-06-03 19:38:03 +00:00
else :
try :
self . _set_fallback_wallpaper ( )
logger . error ( f " The Dictionary { wallpaper_set } does not have sufficient entries, the fallback wallpaper has been set. " )
print ( f " ERROR: The Dictionaty { wallpaper_set } does not have sufficient entries, the fallback wallpaper has been set. " )
2024-06-05 13:07:21 +00:00
return False
2024-06-03 19:38:03 +00:00
except ConfigError :
logger . critical ( f " Dictionary { wallpaper_set } does not have sufficient entries, exciting... " )
raise ConfigError ( f " Dictionary { wallpaper_set } does not have the correct amount of entries, exciting... " )
2024-06-07 20:41:41 +00:00
def validate_config ( self ) - > bool :
2024-06-05 13:07:21 +00:00
if not self . _check_fallback_wallpaper ( ) :
2024-06-06 15:47:44 +00:00
pass
2024-06-05 13:07:21 +00:00
if not self . _check_wallpapers_per_set_and_changing_times ( ) :
exit ( 1 )
if not self . _check_general_validity ( ) :
exit ( 1 )
if not self . _check_wallpaper_dicts ( ) :
exit ( 1 )
if not self . _check_wallpaper_amount ( ) :
exit ( 1 )
2024-06-03 19:38:03 +00:00
logger . debug ( " The config file has been validated successfully (No Errors) " )
2024-06-07 20:41:41 +00:00
return True
2024-06-03 19:38:03 +00:00
class WallpaperLogic ( _ConfigLib ) :
def __init__ ( self ) :
super ( ) . __init__ ( )
self . chosen_wallpaper_set = False
# Returns a list of a split string that contains a changing time from the config file
def _clean_times ( self , desired_time ) - > list :
unclean_times = list ( self . config_changing_times . values ( ) ) [ desired_time ]
return unclean_times . split ( " : " )
def _choose_wallpaper_set ( self ) - > None :
from random import choice as choose_from
self . chosen_wallpaper_set = choose_from ( self . config_used_sets )
self . wallpaper_list = list ( self . config_file [ self . chosen_wallpaper_set ] . values ( ) )
logger . debug ( f " Chose wallpaper set { self . chosen_wallpaper_set } " )
# Verify if a given time is in a given range
def _time_in_range ( self , start , end , x ) - > bool :
if start < = end :
return start < = x < = end
else :
return start < = x or x < end
2024-06-07 20:41:41 +00:00
def _check_system_exitcode ( self , code ) - > bool :
2024-06-05 13:33:02 +00:00
if code != 0 :
try :
2024-06-08 04:36:12 +00:00
self . _set_fallback_wallpaper ( )
2024-06-09 18:19:15 +00:00
logger . error ( f " The wallpaper { self . wallpaper_list [ self . current_time_range ] } has not been found, the fallback wallpaper has been set. Future wallpapers will still attempted to be set. " )
print ( f " ERROR: The wallpaper { self . wallpaper_list [ self . current_time_range ] } has not been found, the fallback wallpaper has been set. Future wallpapers will still attempted to be set. " )
2024-06-05 13:33:02 +00:00
return False
except ConfigError :
2024-06-09 18:19:15 +00:00
logger . error ( f " The wallpaper { self . wallpaper_list [ self . current_time_range ] } has not been found and no fallback wallpaper has been set. Future wallpapers will still attempted to be set. " )
print ( f " ERROR: The wallpaper { self . wallpaper_list [ self . current_time_range ] } has not been found and no fallback wallpaper has been set. Future wallpapers will still attempted to be set. " )
2024-06-05 13:33:02 +00:00
return False
else :
2024-06-09 19:25:26 +00:00
logger . info ( f " The wallpaper { self . wallpaper_list [ self . current_time_range ] } has been set. " )
2024-06-05 13:33:02 +00:00
return True
2024-06-03 19:38:03 +00:00
def _notify_user ( self ) :
system ( " notify-send ' Wallman ' ' A new Wallpaper has been set. ' " )
logger . debug ( " Sent desktop notification. " )
2024-06-05 13:33:02 +00:00
def set_wallpaper_by_time ( self ) - > bool :
2024-06-03 19:38:03 +00:00
# Ensure use of a consistent wallpaper set
if self . chosen_wallpaper_set is False :
self . _choose_wallpaper_set ( )
for time_range in range ( self . config_total_changing_times - 1 ) :
2024-06-09 18:19:15 +00:00
self . current_time_range = time_range # Store current time for better debugging output
2024-06-03 19:38:03 +00:00
clean_time = self . _clean_times ( time_range )
clean_time_two = self . _clean_times ( time_range + 1 )
# Check if the current time is between a given and the following changing time and if so, set that wallpaper. If not, keep trying.
if self . _time_in_range ( time ( int ( clean_time [ 0 ] ) , int ( clean_time [ 1 ] ) , int ( clean_time [ 2 ] ) ) , time ( int ( clean_time_two [ 0 ] ) , int ( clean_time_two [ 1 ] ) , int ( clean_time_two [ 2 ] ) ) , datetime . now ( ) . time ( ) ) :
2024-06-06 15:47:44 +00:00
system ( f " feh --bg-scale --no-fehbg --quiet { self . wallpaper_list [ time_range ] } " )
2024-06-05 13:33:02 +00:00
exitcode = system ( f " feh --bg-scale --no-fehbg --quiet { self . wallpaper_list [ time_range ] } " )
has_wallpaper_been_set = self . _check_system_exitcode ( exitcode )
2024-06-03 19:38:03 +00:00
if self . config_notify :
self . _notify_user ( )
2024-06-05 13:33:02 +00:00
return has_wallpaper_been_set
2024-06-03 19:38:03 +00:00
else :
continue
2024-06-06 15:47:44 +00:00
system ( f " feh --bg-scale --no-fehbg { self . wallpaper_list [ - 1 ] } " )
2024-06-05 13:33:02 +00:00
exitcode = system ( f " feh --bg-scale --no-fehbg { self . wallpaper_list [ - 1 ] } " )
has_wallpaper_been_set = self . _check_system_exitcode ( exitcode )
2024-06-03 19:38:03 +00:00
if self . config_notify :
self . _notify_user ( )
2024-06-05 13:33:02 +00:00
return has_wallpaper_been_set
2024-06-03 19:38:03 +00:00
def schedule_wallpapers ( self ) :
scheduler = BlockingScheduler ( )
# Create a scheduled job for every changing time
for changing_time in range ( len ( self . config_changing_times ) ) :
clean_time = self . _clean_times ( changing_time )
scheduler . add_job ( self . set_wallpaper_by_time , trigger = CronTrigger ( hour = clean_time [ 0 ] , minute = clean_time [ 1 ] , second = clean_time [ 2 ] ) )
scheduler . start ( )
logger . info ( " The scheduler has been started. " )