Compare commits
3 commits
6a3354061d
...
590a8e5214
Author | SHA1 | Date | |
---|---|---|---|
|
590a8e5214 | ||
|
6f62d79adb | ||
|
b7ce0a31cb |
4 changed files with 220 additions and 98 deletions
71
README.org
71
README.org
|
@ -1,36 +1,71 @@
|
|||
#+TITLE: Readme
|
||||
#+AUTHOR: Emma Nora Theuer
|
||||
|
||||
* Wallman
|
||||
* Overwiev
|
||||
** What is this?
|
||||
This is my project wallman. Wallman is a simple and minimalistic python script used for setting Dynamic Wallpapers on minimalist X11 Window Managers and Wayland compositors.
|
||||
The version you are currently looking at is an old prototype. It hardcodes the wallpaper sets and locations of the wallpapers as well as changing times in source code. As such, it's a hassle to configure. This should be replaced with the new version soon.
|
||||
This is my project wallman. Wallman is a simple python program used for setting Dynamic Wallpapers on minimalist X11 Window Managers and Wayland compositors.
|
||||
This version is an early Alpha. As of now, it supports the most important features for my usecase, those being randomly selected wallpaper sets and wallpaper sets that change by the time of day. The program is not modular yet and I would expect a lot of bugs related to the configuration file. Just how it is, I'm working on it.
|
||||
As such, please make absolutely sure you follow the instructions on how to write the config file very closely. I will implement better config handling with more meaningful error output in the future. For now, follow everything really closely and read the logs if needed. If you do that, it /should/ work.
|
||||
|
||||
** What can it do?
|
||||
Wallman currently has two main features:
|
||||
Wallman currently has three main features:
|
||||
+ Reading configuration details from a TOML file
|
||||
+ Choosing from a set of Wallpapers and then setting the rest of the wallpapers accordingly
|
||||
+ Settings Wallpapers at a specific time of the day
|
||||
|
||||
Reading from a TOML config will be added very soon.
|
||||
|
||||
** Requirements:
|
||||
+ Python 3.5+
|
||||
+ The APScheduler library for python (install python-apscheduler on most Linux distributions, otherwise, install apscheduler with pip or your preferred python package manager of choice)
|
||||
+ feh
|
||||
* Installation
|
||||
** Requirements
|
||||
+ Python 3.11 or newer (Required because of tomllib)
|
||||
+ APScheduler (Install python-apscheduler or APScheduler, depending on the package manager)
|
||||
+ feh (Used for setting the wallpapers, hard dependency)
|
||||
|
||||
** How to install it?
|
||||
+ Clone this git repo
|
||||
+ Write a .desktop file or add a shebang to the beginning and then add it to /usr/bin
|
||||
+ Create a log file:
|
||||
+ Create a log file and a configuration file:
|
||||
#+BEGIN_SRC shell
|
||||
mkdir -p ~/.local/share/wallman
|
||||
mkdir -p ~/.config/wallman
|
||||
touch ~/.local/share/wallman/wallman.log
|
||||
cp sample_config.toml ~/.config/wallman/wallman.toml
|
||||
#+END_SRC
|
||||
+ Replace all the hardcoded things with your wallpapers and wallpaper sets (Optionally, replace the loglevel because you probably don't want debug)
|
||||
+ Adjust the loglevel to your liking. This will be part of the config or a command line argument soon.
|
||||
+ Profit
|
||||
|
||||
** TODO:
|
||||
- Add support for reading from a TOML config file (Should be added very soon)
|
||||
- Add support for setting wallpapers that change with the weather
|
||||
- Add support for live wallpapers
|
||||
- Drop the feh dependency and use python-xlib and pywlroots instead
|
||||
* Configuration
|
||||
This is a short guide on how to correctly configure wallman. Look in the sample config for additional context.
|
||||
** TOML Dictionaries
|
||||
First of all, the config file is structured via different TOML dictionaries. There are two TOML dictionaries: wallpaper_sets and changing_times that must be present in every config. Aside from that, further dictionaries are needed depending on how wallman is configured. You need to create a dictionary with the name of each wallpaper set defined in the used_sets list (more on that later). You should probably just configure wallman by editing the sample config as it is by far the easiest way to do it.
|
||||
*** wallpaper_sets
|
||||
In wallpaper_sets, you need to always define 3 variables:
|
||||
+ enabled: bool
|
||||
A simple switch that states if you want to use different sets of wallpapers or not.
|
||||
+ used_sets: list
|
||||
A list that includes the names of the wallpaper sets you want to use. If you want to use only one, the list should have one entry.
|
||||
+ wallpapers_per_set: int
|
||||
The amount of wallpapers that you use in each set. It should be an integer.
|
||||
|
||||
*** changing_times
|
||||
The changing_times dictionary is used to specify the times of the day when your wallpaper is switched. The names of the keys do not matter here, the values must always be strings in the "XX:YY:ZZ" 24 hour time system. use 00:00:00 for midnight. Note that XX should be in the range of 00-23 and YY and ZZ should be in the range of 00-59.
|
||||
|
||||
*** The other dictionaries
|
||||
The other dictionaries must always have the names of the wallpaper sets from used_sets. If you have one wallpaper set, you need one additional dictionary, if you have two you need two etc. The standard config uses nature and anime, these names can be whatever you please as long as they are the same as the ones specified in used_sets.
|
||||
The keys in the dictionary once again do not matter, the names of the keys in each dictionary must be strings and be absolute paths. They should not include spaces unless prefaced by a backslash.
|
||||
|
||||
|
||||
* TODOs
|
||||
** Structuring
|
||||
+ Write unittests
|
||||
+ Write a setup.py
|
||||
+ Improve the general directory layout
|
||||
|
||||
** Technical Details
|
||||
+ Improve Modularity
|
||||
+ Make the enabled flag in wallpaper_sets actually useful by making the used_sets field optional
|
||||
+ Improve output in cases of error
|
||||
+ Add support for different loglevels in the config file or as a command line argument
|
||||
+ Drop the feh dependecy and set wallpapers using pywlroots or python-xlib
|
||||
|
||||
** Features
|
||||
+ Add support for wallpapers that dynamically change with the time of day (Morning, noon, evening, night or light levels) rather than to times set in the config
|
||||
+ Add support for wallpapers that change by the weather
|
||||
+ Add support for live wallpapers
|
||||
|
|
32
sample_config.toml
Normal file
32
sample_config.toml
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Here, define if you want to use different sets of wallpapers, and if yes,
|
||||
# what those sets are called and how many wallpapers are included per set.
|
||||
[wallpaper_sets]
|
||||
enabled = true
|
||||
used_sets = ["anime", "nature"]
|
||||
wallpapers_per_set = 5
|
||||
|
||||
# Enter the hours at which you want the wallpaper to change.
|
||||
# If the script is called between one of this times, it will go back to the previous time.
|
||||
[changing_times]
|
||||
first = "01:00:00"
|
||||
second = "06:00:00"
|
||||
third = "10:00:00"
|
||||
fourth = "17:00:00"
|
||||
fifth = "21:00:00"
|
||||
|
||||
# The first set in your list of used wallpaper sets.
|
||||
# Pay attention that the name used here must equal to that used above.
|
||||
[anime]
|
||||
pap0 = "/path/to/anime/paper"
|
||||
pap1 = "/path/to/anime/paper"
|
||||
pap2 = "/path/to/anime/paper"
|
||||
pap3 = "/path/to/anime/paper"
|
||||
pap4 = "/path/to/anime/paper"
|
||||
|
||||
# The second set in your list of wallpaper sets. See above.
|
||||
[nature]
|
||||
pap0 = "/path/to/paper"
|
||||
pap1 = "/path/to/paper"
|
||||
pap2 = "/path/to/paper"
|
||||
pap3 = "/path/to/paper"
|
||||
pap4 = "/path/to/paper"
|
87
wallman.py
87
wallman.py
|
@ -1,85 +1,12 @@
|
|||
from os import system, chdir, getenv
|
||||
from datetime import datetime
|
||||
from apscheduler.schedulers.blocking import BlockingScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
import logging
|
||||
|
||||
# Setup logging
|
||||
chdir(str(getenv("HOME")) + "/.local/share/wallman")
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(filename="wallman.log", encoding="utf-8", level=logging.DEBUG)
|
||||
|
||||
# Initialize the global chosen_wallpaper_set variable
|
||||
global chosen_wallpaper_set
|
||||
chosen_wallpaper_set = False
|
||||
|
||||
def choose_wallpaper_set():
|
||||
# Random library for choosing wallpaper set
|
||||
from random import choice as choose_from
|
||||
wallpaper_sets = ["nature", "anime"]
|
||||
selected_set = choose_from(wallpaper_sets)
|
||||
return selected_set
|
||||
|
||||
def set_wallpaper_once():
|
||||
# Set the respective Wallpaper for each hour of the day
|
||||
# Deepnight Wallpaper
|
||||
if datetime.now().hour in range(0, 5):
|
||||
system("feh --bg-scale --no-fehbg /home/emma/Bilder/Hintergründe/wideAuroraNight.jpg")
|
||||
# Morning Wallpaper
|
||||
elif datetime.now().hour in range(6, 9):
|
||||
system("feh --bg-scale --no-fehbg /home/emmma/Bilder/Hintergründe/widePeninsulasMorning.jpg")
|
||||
# Dayhour Wallpaper
|
||||
elif datetime.now().hour in range(10, 17):
|
||||
system("feh --bg-scale --no-fehbg /home/emma/Bilder/Hintergründe/wideMountainDay.jpg")
|
||||
# Evening Wallpaper
|
||||
elif datetime.now().hour in range(18, 21):
|
||||
system("feh --bg-scale --no-fehbg /home/emma/Bilder/Hintergründe/wideLakeEvening.jpg")
|
||||
# Early Night Wallpaper
|
||||
else:
|
||||
system("feh --bg-scale --no-fehbg /home/emma/Bilder/Hintergründe/wideMountainEveningNight.jpg")
|
||||
|
||||
def set_anime_wallpaper_once():
|
||||
# Set the respective Wallpaper for each hour of the day
|
||||
# Deepnight Wallpaper
|
||||
if datetime.now().hour in range(0, 5):
|
||||
system("feh --bg-scale --no-fehbg '/home/emma/Bilder/Hintergründe/Anime Wallpaper/wideSniperNight.jpg'")
|
||||
# Morning Wallpaper
|
||||
elif datetime.now().hour in range(6, 9):
|
||||
system("feh --bg-scale --no-fehbg '/home/emma/Bilder/Hintergründe/Anime Wallpaper/wideDepressionMorning.jpg'")
|
||||
# Daytime Wallpaper
|
||||
elif datetime.now().hour in range(10, 17):
|
||||
system("feh --bg-scale --no-fehbg '/home/emma/Bilder/Hintergründe/Anime Wallpaper/WideStreetDay.jpg'")
|
||||
# Evening Wallpaper
|
||||
elif datetime.now().hour in range(18, 21):
|
||||
system("feh --bg-scale --no-fehbg '/home/emma/Bilder/Hintergründe/Anime Wallpaper/WideDualityEvening.jpg'")
|
||||
# Early Night Wallpaper
|
||||
else:
|
||||
system("feh --bg-scale --no-fehbg '/home/emma/Bilder/Hintergründe/Anime Wallpaper/wideNameNight.png'")
|
||||
|
||||
def set_chosen_wallpaper():
|
||||
global chosen_wallpaper_set
|
||||
if chosen_wallpaper_set == False:
|
||||
chosen_wallpaper_set = choose_wallpaper_set()
|
||||
logger.info(f"chose wallpaper set: {chosen_wallpaper_set}.")
|
||||
if chosen_wallpaper_set == "nature":
|
||||
set_wallpaper_once()
|
||||
logger.info(f"Set {chosen_wallpaper_set} wallpaper.")
|
||||
elif chosen_wallpaper_set == "anime":
|
||||
logger.info(f"Set {chosen_wallpaper_set} wallpaper.")
|
||||
set_anime_wallpaper_once()
|
||||
else:
|
||||
system("feh --bg-scale --no-fehbg /home/emma/Bilder/Hintergründe/wideFallbackScreen.jpg")
|
||||
logger.warning("Something went wrong and the fallback wallpaper has been set.")
|
||||
|
||||
def schedule_wallpapers():
|
||||
scheduler = BlockingScheduler()
|
||||
scheduler.add_job(set_chosen_wallpaper, trigger=CronTrigger(hour="1,6,10,18,22"))
|
||||
scheduler.start()
|
||||
logger.info("The scheduler has been started.")
|
||||
#!/usr/bin/env python3
|
||||
import wallman_lib
|
||||
|
||||
def main():
|
||||
set_chosen_wallpaper()
|
||||
schedule_wallpapers()
|
||||
validator = wallman_lib.ConfigValidity()
|
||||
logic = wallman_lib.WallpaperLogic()
|
||||
validator.validate_config()
|
||||
logic.set_wallpaper_by_time()
|
||||
logic.schedule_wallpapers()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
128
wallman_lib.py
Normal file
128
wallman_lib.py
Normal file
|
@ -0,0 +1,128 @@
|
|||
#!/usr/bin/env python3
|
||||
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
|
||||
|
||||
# Setting up essential variables
|
||||
global chosen_wallpaper_set
|
||||
chosen_wallpaper_set = False
|
||||
|
||||
# setup logging
|
||||
chdir(str(getenv("HOME")) + "/.local/share/wallman/")
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(filename="wallman.log", encoding="utf-8", level=logging.DEBUG)
|
||||
|
||||
# read config
|
||||
# a = list(data["changing_times"].values())
|
||||
# print(a[0])
|
||||
|
||||
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
|
||||
# a = list(data["changing_times"].values())
|
||||
# print(a[0])
|
||||
|
||||
def __init__(self):
|
||||
self.config_file: dict = self._initialize_config() # Full config
|
||||
# Dictionaries
|
||||
self.config_wallpaper_sets: dict = self.config_file["wallpaper_sets"]
|
||||
self.config_changing_times: dict = self.config_file["changing_times"]
|
||||
# Values in Dicts
|
||||
self.config_wallpaper_sets_enabled: bool = self.config_wallpaper_sets["enabled"]
|
||||
self.config_used_sets: list = self.config_wallpaper_sets["used_sets"]
|
||||
self.config_wallpapers_per_set: int = self.config_wallpaper_sets["wallpapers_per_set"]
|
||||
self.config_total_changing_times: int = len(self.config_changing_times)
|
||||
|
||||
class ConfigValidity(_ConfigLib):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def _check_wallpapers_per_set_and_changing_times(self) -> None:
|
||||
# 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")
|
||||
else:
|
||||
logger.error("The amount of changing times and the amount of wallpapers per set does not match.")
|
||||
raise ConfigError("Please provide an amount of changing_times equal to wallpapers_per_set.")
|
||||
|
||||
def _check_wallpaper_sets_validity(self) -> None:
|
||||
if len(self.config_wallpaper_sets) != 3:
|
||||
logger.error("An insufficient amount of parameters for wallpaper_sets has been provided, exiting...")
|
||||
raise ConfigError("wallpaper_sets should have exactly 3 elements")
|
||||
|
||||
def _check_wallpaper_dicts(self)-> None:
|
||||
# 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.")
|
||||
else:
|
||||
logger.error(f"No dictionary {wallpaper_set} has been found in the config.")
|
||||
raise ConfigError(f"The dictionary {wallpaper_set} has not been found in the config")
|
||||
|
||||
def _check_wallpaper_amount(self) -> None:
|
||||
# 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.")
|
||||
else:
|
||||
logger.error(f"Dictionary {wallpaper_set} does not have sufficient entries")
|
||||
raise ConfigError(f"Dictionary {wallpaper_set} does not have the correct amount of entries")
|
||||
|
||||
def validate_config(self) -> None:
|
||||
self._check_wallpapers_per_set_and_changing_times()
|
||||
self._check_wallpaper_sets_validity()
|
||||
self._check_wallpaper_dicts()
|
||||
self._check_wallpaper_amount()
|
||||
logger.debug("The config file has been validated successfully (No Errors)")
|
||||
|
||||
class WallpaperLogic(_ConfigLib):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
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
|
||||
global chosen_wallpaper_set
|
||||
chosen_wallpaper_set = choose_from(self.config_used_sets)
|
||||
self.wallpaper_list = list(self.config_file[chosen_wallpaper_set].values())
|
||||
logger.debug(f"Chose wallpaper set {chosen_wallpaper_set}")
|
||||
|
||||
def _time_in_range(self, start, end, x) -> bool:
|
||||
if start <= end:
|
||||
return start <= x <= end
|
||||
else:
|
||||
return start <= x or x < end
|
||||
|
||||
def set_wallpaper_by_time(self) -> None:
|
||||
if chosen_wallpaper_set is False:
|
||||
self._choose_wallpaper_set()
|
||||
for time_range in range(self.config_total_changing_times - 1):
|
||||
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()):
|
||||
system(f"feh --bg-scale --no-fehbg {self.wallpaper_list[time_range]}")
|
||||
return
|
||||
else:
|
||||
continue
|
||||
system(f"feh --bg-scale --no-fehbg {self.wallpaper_list[-1]}")
|
||||
|
||||
def schedule_wallpapers(self):
|
||||
scheduler = BlockingScheduler()
|
||||
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.")
|
Loading…
Reference in a new issue