Compare commits

..

No commits in common. "main" and "wallman-releases" have entirely different histories.

17 changed files with 119 additions and 451 deletions

View file

@ -1,6 +1,2 @@
graft src graft src
include src/*.py
graft icons
graft distfiles
include sample_config.toml include sample_config.toml
exclude README.org

View file

@ -12,7 +12,6 @@ Wallman currently has three main features:
+ Reading configuration details from a TOML file + Reading configuration details from a TOML file
+ Choosing from a set of Wallpapers and then setting the rest of the wallpapers accordingly + Choosing from a set of Wallpapers and then setting the rest of the wallpapers accordingly
+ Settings Wallpapers at a specific time of the day + Settings Wallpapers at a specific time of the day
+ Be controlled via a systray
* Installation * Installation
** Depedencies ** Depedencies
@ -22,54 +21,30 @@ Wallman currently has three main features:
+ feh (Used for setting the wallpapers, hard dependency) + feh (Used for setting the wallpapers, hard dependency)
*** Optional *** Optional
+ libnotify (for desktop notification support) + libnotify (for desktop notification support)
+ pillow (For systray support)
+ pystray (For systray support)
*** Build dependencies *** Build dependencies
+ setuptools + setuptools
+ build + build
** Installing with package Manager
*** Gentoo
This program, as of now, can be installed very easily on gentoo. Just follow these instructions:
#+BEGIN_SRC shell
git clone https://git.entheuer.de/emma/Wallman.git
doas eselect repository create wallman
doas cp -rf Wallman/distfiles/Gentoo/wallman /var/db/repos/
doas emerge -av wallman
#+END_SRC
A proper portage overlay will be created soon, so that updates can be handled automatically.
*** Arch Linux
Support for Arch Linux will be added soon.
*** Others
I will potentially write a version for nixpkgs and will also bundle wallman as a flatpak.
** Installing with pip ** Installing with pip
Wallman is available on PyPI. Simply run: Wallman is available on PYPI. Simply run:
#+BEGIN_SRC shell #+BEGIN_SRC shell
pip install wallman pip install wallman
#+END_SRC #+END_SRC
** Installing with package Manager
Versions in the AUR and an ebuild for Gentoo will be added soon. A flatpak and Nixpkgs version are on the horizon, too.
** Installing manually ** Installing manually
+ Install libnotify and feh from your package manager + Clone this git repo
+ Create a log file and a configuration file:
#+BEGIN_SRC shell #+BEGIN_SRC shell
pip install APScheduler pystray pillow mkdir -p ~/.local/share/wallman
git clone https://git.entheuer.de/emma/Wallman.git
cd Wallman/
sudo mkdir -p /var/log/wallman
sudo chmod 733 /var/log/wallman
mkdir -p ~/.config/wallman mkdir -p ~/.config/wallman
touch ~/.local/share/wallman/wallman.log
cp sample_config.toml ~/.config/wallman/wallman.toml cp sample_config.toml ~/.config/wallman/wallman.toml
doas mkdir -p /etc/wallman/
cp -R icons/ /etc/wallman/
doas cp src/wallman.py /usr/bin/wallman
doas cp src/wallman_lib.py /usr/bin/wallman_lib.py
doas cp src/wallman_systray.py /usr/bin/wallman_systray.py
doas chmod +x /usr/bin/wallman
#+END_SRC #+END_SRC
+ Edit the sample config + Edit the sample config
+ (Optional): Adjust the loglevel to your liking. This will be part of the config or a command line argument soon.
+ Profit + Profit
* Configuration * Configuration
@ -77,7 +52,7 @@ This is a short guide on how to correctly configure wallman. Look in the sample
** TOML Dictionaries ** TOML Dictionaries
First of all, the config file is structured via different TOML dictionaries. There are two TOML dictionaries: general 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. First of all, the config file is structured via different TOML dictionaries. There are two TOML dictionaries: general 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.
*** general *** general
In general, you need to always define 3 variables and you can optionally add three more: In general, you need to always define 3 variables and you can optionally add two more:
+ enable_wallpaper_sets: bool + enable_wallpaper_sets: bool
A simple switch that states if you want to use different sets of wallpapers or not. A simple switch that states if you want to use different sets of wallpapers or not.
+ used_sets: list + used_sets: list
@ -87,11 +62,7 @@ In general, you need to always define 3 variables and you can optionally add thr
+ Optional: notify: bool + Optional: notify: bool
This defaults to "false". Enable to set send a desktop notification when the wallpaper is changed. The program will still work correctly, even if this option is not defined at all. This defaults to "false". Enable to set send a desktop notification when the wallpaper is changed. The program will still work correctly, even if this option is not defined at all.
+ Optional: fallback_wallpaper: bool + Optional: fallback_wallpaper: bool
Wallpaper to be set if an error is found in the config or the wallpaper intended to be set cannot be found. Defaults to None. If none is set and the config has been written incorrectly, a ConfigError is raised and the program is exited. If an error in the config occurs but the fallback wallpaper has been defined, it will be set and wallman will exit with Code 1. If The config is written correctly but the wallpaper intended to be set can't be found, wallman will set the fallback wallpaper and continue to try setting future wallpapers. Wallpaper to be set if an error is found in the config. Defaults to None. If none is set and the config is written incorrectly, a ConfigError is raised and the program is exited. If an error in the config occurs but the fallback wallpaper has been defined, it will be set and wallman will exit with Code 1.
+ Optional: loglevel: string
Loglevel to be used by wallman. Defaults to INFO. Choices MUST be DEBUG, INFO, WARNING, ERROR or CRITICAL. Using any capitalization is valid, all caps is reccomended. Wallman will crash if a value is specified that is not one of the specified ones.
+ Optional: systray: bool
This defaults to "true". This enables support for a systray that has the features to re-set your wallpaper (Mostly useful if feh shits itself or if you want to set the correct wallpaper for a specific time of day after your device was suspended) without rerolling the wallpaper set used, a button to reroll and then re-set the wallpaper, as well as a Quit button. Disable this to save a very tiny amount of memory.
*** changing_times *** 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 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.
@ -107,11 +78,13 @@ The keys in the dictionary once again do not matter, the names of the keys in ea
+ Add documentation for developers + Add documentation for developers
** Technical Details ** Technical Details
+ Improve Modularity (Partially done) + Improve Modularity
+ Make the enabled flag in wallpaper_sets actually useful by making the used_sets field optional + Make the enabled flag in wallpaper_sets actually useful by making the used_sets field optional
+ 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 + Drop the feh dependecy and set wallpapers using pywlroots or python-xlib
** Features ** Features
+ Add support for setting a fallback wallpaper if a wallpaper the user set is not found
+ 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 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 wallpapers that change by the weather
+ Add support for live wallpapers + Add support for live wallpapers

View file

@ -1 +0,0 @@
DIST wallman-1.4.2.4.tar.gz 871198 BLAKE2B 02ccfa69e14b73eff667ecf4707fcebd08ba63362a6e25ae77bef96ff77d8723e417e14e8586f2b8a9e0b23f29a1be8b7e1c0cab10a7f242dc8a7b2fe418cfe4 SHA512 87f2cad40f6db418dc4a94a259b4c491e69fc0f7bdf121196679270492227449f79751f40467d6f50e4bf7598e38d78853818ac9df8cfa96a7dc34698128ac71

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE pkgmetadata SYSTEM "https://www.gentoo.org/dtd/metadata.dtd">
<pkgmetadata>
<maintainer type="person">
<name>Emma Nora Theuer</name>
<email>gentoo@entheuer.de</email>
</maintainer>
<upstream>
<remote-id type="pypi">Wallman</remote-id>
</upstream>
<longdescription>
Wallman is a small python program meant to bring dynamic wallpapers
to standalone X11 Window managers and Wayland compositors.
It uses APScheduler in the background. For setting wallpaperss
It currently relies on feh in the background. libnotify is used for
Desktop notifications.
Wallman reads it's configuration data from a TOML file and logs to
~/.local/share/wallman/wallman.log
</longdescription>
</pkgmetadata>

View file

@ -1,70 +0,0 @@
# Copyright 1999-2024 Gentoo Authors
# Distributed under the terms of the MIT License
EAPI=8
DISTUTILS_USE_PEP517=setuptools
PYTHON_COMPAT=( python3_{11,12} )
inherit distutils-r1 pypi
DESCRIPTION="A python program that sets dynamic wallpapers on minimalistic Window Managers."
HOMEPAGE="https://git.entheuer.de/emma/wallman/"
SRC_URI="$(pypi_sdist_url "${PN^}" "${PV}")"
LICENSE="MIT"
SLOT="0"
KEYWORDS="~amd64 ~x86"
RDEPEND="
dev-python/APScheduler[${PYTHON_USEDEP}]
dev-python/pillow[${PYTHON_USEDEP}]
dev-python/pystray[${PYTHON_USEDEP}]
media-gfx/feh
x11-libs/libnotify
"
BDEPEND="
dev-python/setuptools[${PYTHON_USEDEP}]
dev-python/wheel[${PYTHON_USEDEP}]
dev-python/certifi[${PYTHON_USEDEP}]
"
src_prepare() {
distutils-r1_python_prepare_all
}
#src_prepare() {
# mv src/* . || die "Failed to move source files"
#}
python_compile() {
distutils-r1_python_compile -j1
}
python_install() {
distutils-r1_python_install
# Add a symlink to make the script callable from the commandline
local scriptname="wallman.py"
local target="/usr/bin/wallman"
local scriptpath="$(python_get_sitedir)/${scriptname}"
fperms +x "${scriptpath}"
dosym "${scriptpath}" "${target}"
# Copy files into /etc/wallman
dodir /etc/wallman
insinto /etc/wallman
newins "${S}/sample_config.toml" "wallman.toml"
doins -r "${S}/icons/" "icons/"
# Create logfile directory
dodir /var/log/wallman
keepdir /var/log/wallman
fperms 0733 /var/log/wallman
# Copy .desktop file into the appropriate location
insinto /usr/share/applications
newins "${S}/distfiles/wallman.desktop" "wallman.desktop"
}
#src_install() {
#}
pkg_postinst() {
elog "Wallman has been installed. A sample configuration file called wallman.toml is located in /etc/wallman. Copy that file into ~/.config/wallman/wallman.toml to configure wallman."
elog "A log file for Wallman can be found in /etc/log/wallman"
}

View file

@ -1,3 +0,0 @@
masters = gentoo
thin-manifests = true
sign-manifests = false

View file

@ -1 +0,0 @@
8

View file

@ -1 +0,0 @@
wallman

View file

@ -1,8 +0,0 @@
[Desktop Entry]
Name=wallman
Comment=run wallman
Exec=wallman
Icon=/etc/wallman/icons/WallmanLogo.jpg
Terminal=false
Type=Application
Categories=Utility;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 833 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

View file

@ -1,38 +1,34 @@
# Table of Contents # Table of Contents
1. [Overwiev](#orga779aae) 1. [Overwiev](#org19ef638)
1. [What is this?](#orgf7b412d) 1. [What is this?](#org148aad0)
2. [What can it do?](#org935ec0e) 2. [What can it do?](#org0041bf1)
2. [Installation](#org022a1fd) 2. [Installation](#orgc40ee8b)
1. [Depedencies](#org90be55c) 1. [Depedencies](#org2cb62ef)
1. [Always Required](#orgadb7aa8) 1. [Always Required](#org0d0b0f7)
2. [Optional](#org407e4c9) 2. [Optional](#orgf04ec77)
3. [Build dependencies](#orgab711aa) 3. [Build dependencies](#org3951087)
2. [Installing with package Manager](#org2a19ad0) 2. [Installing with pip](#org24bde1c)
1. [Gentoo](#org6686b86) 3. [Installing with package Manager](#org9575c55)
2. [Arch Linux](#org4eb492d) 4. [Installing manually](#org2c63e0f)
3. [Others](#orga6b4e6a) 3. [Configuration](#org812c501)
3. [Installing with pip](#org1b5b1b3) 1. [TOML Dictionaries](#org51e3333)
4. [Installing manually](#org050e242) 1. [general](#org79747a5)
3. [Configuration](#orgf368a5c) 2. [changing<sub>times</sub>](#orgc425813)
1. [TOML Dictionaries](#org05ab08e) 3. [The other dictionaries](#org9f47b06)
1. [general](#orgfa7800e) 4. [TODOs](#orgc26c243)
2. [changing<sub>times</sub>](#org2778aa9) 1. [Structuring](#org8dd22d4)
3. [The other dictionaries](#orgf21f2d8) 2. [Technical Details](#org6103451)
4. [TODOs](#org8cbebae) 3. [Features](#org38d78ba)
1. [Structuring](#orgb43553d)
2. [Technical Details](#org0a633fb)
3. [Features](#org1c5725d)
<a id="orga779aae"></a> <a id="org19ef638"></a>
# Overwiev # Overwiev
<a id="orgf7b412d"></a> <a id="org148aad0"></a>
## What is this? ## What is this?
@ -41,7 +37,7 @@ This version is an early Alpha. As of now, it supports the most important featur
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. 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.
<a id="org935ec0e"></a> <a id="org0041bf1"></a>
## What can it do? ## What can it do?
@ -50,20 +46,19 @@ Wallman currently has three main features:
- Reading configuration details from a TOML file - Reading configuration details from a TOML file
- Choosing from a set of Wallpapers and then setting the rest of the wallpapers accordingly - Choosing from a set of Wallpapers and then setting the rest of the wallpapers accordingly
- Settings Wallpapers at a specific time of the day - Settings Wallpapers at a specific time of the day
- Be controlled via a systray
<a id="org022a1fd"></a> <a id="orgc40ee8b"></a>
# Installation # Installation
<a id="org90be55c"></a> <a id="org2cb62ef"></a>
## Depedencies ## Depedencies
<a id="orgadb7aa8"></a> <a id="org0d0b0f7"></a>
### Always Required ### Always Required
@ -72,16 +67,14 @@ Wallman currently has three main features:
- feh (Used for setting the wallpapers, hard dependency) - feh (Used for setting the wallpapers, hard dependency)
<a id="org407e4c9"></a> <a id="orgf04ec77"></a>
### Optional ### Optional
- libnotify (for desktop notification support) - libnotify (for desktop notification support)
- pillow (For systray support)
- pystray (For systray support)
<a id="orgab711aa"></a> <a id="org3951087"></a>
### Build dependencies ### Build dependencies
@ -89,91 +82,58 @@ Wallman currently has three main features:
- build - build
<a id="org2a19ad0"></a> <a id="org24bde1c"></a>
## Installing with package Manager
<a id="org6686b86"></a>
### Gentoo
This program, as of now, can be installed very easily on gentoo. Just follow these instructions:
git clone https://git.entheuer.de/emma/Wallman.git
doas eselect repository create wallman
doas cp -rf Wallman/distfiles/Gentoo/wallman /var/db/repos/
doas emerge -av wallman
A proper portage overlay will be created soon, so that updates can be handled automatically.
<a id="org4eb492d"></a>
### Arch Linux
Support for Arch Linux will be added soon.
<a id="orga6b4e6a"></a>
### Others
I will potentially write a version for nixpkgs and will also bundle wallman as a flatpak.
<a id="org1b5b1b3"></a>
## Installing with pip ## Installing with pip
Wallman is available on PyPI. Simply run: Wallman is available on PYPI. Simply run:
pip install wallman pip install wallman
<a id="org050e242"></a> <a id="org9575c55"></a>
## Installing with package Manager
Versions in the AUR and an ebuild for Gentoo will be added soon. A flatpak and Nixpkgs version are on the horizon, too.
<a id="org2c63e0f"></a>
## Installing manually ## Installing manually
- Install libnotify and feh from your package manager - Clone this git repo
- Create a log file and a configuration file:
pip install APScheduler pystray pillow mkdir -p ~/.local/share/wallman
git clone https://git.entheuer.de/emma/Wallman.git
cd Wallman/
sudo mkdir -p /var/log/wallman
sudo chmod 733 /var/log/wallman
mkdir -p ~/.config/wallman mkdir -p ~/.config/wallman
touch ~/.local/share/wallman/wallman.log
cp sample_config.toml ~/.config/wallman/wallman.toml cp sample_config.toml ~/.config/wallman/wallman.toml
doas mkdir -p /etc/wallman/
cp -R icons/ /etc/wallman/
doas cp src/wallman.py /usr/bin/wallman
doas cp src/wallman_lib.py /usr/bin/wallman_lib.py
doas cp src/wallman_systray.py /usr/bin/wallman_systray.py
doas chmod +x /usr/bin/wallman
- Edit the sample config - Edit the sample config
- (Optional): Adjust the loglevel to your liking. This will be part of the config or a command line argument soon.
- Profit - Profit
<a id="orgf368a5c"></a> <a id="org812c501"></a>
# Configuration # Configuration
This is a short guide on how to correctly configure wallman. Look in the sample config for additional context. This is a short guide on how to correctly configure wallman. Look in the sample config for additional context.
<a id="org05ab08e"></a> <a id="org51e3333"></a>
## TOML Dictionaries ## TOML Dictionaries
First of all, the config file is structured via different TOML dictionaries. There are two TOML dictionaries: general and changing<sub>times</sub> 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<sub>sets</sub> 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. First of all, the config file is structured via different TOML dictionaries. There are two TOML dictionaries: general and changing<sub>times</sub> 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<sub>sets</sub> 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.
<a id="orgfa7800e"></a> <a id="org79747a5"></a>
### general ### general
In general, you need to always define 3 variables and you can optionally add three more: In general, you need to always define 3 variables and you can optionally add two more:
- enable<sub>wallpaper</sub><sub>sets</sub>: bool - enable<sub>wallpaper</sub><sub>sets</sub>: bool
A simple switch that states if you want to use different sets of wallpapers or not. A simple switch that states if you want to use different sets of wallpapers or not.
@ -184,21 +144,17 @@ In general, you need to always define 3 variables and you can optionally add thr
- Optional: notify: bool - Optional: notify: bool
This defaults to &ldquo;false&rdquo;. Enable to set send a desktop notification when the wallpaper is changed. The program will still work correctly, even if this option is not defined at all. This defaults to &ldquo;false&rdquo;. Enable to set send a desktop notification when the wallpaper is changed. The program will still work correctly, even if this option is not defined at all.
- Optional: fallback<sub>wallpaper</sub>: bool - Optional: fallback<sub>wallpaper</sub>: bool
Wallpaper to be set if an error is found in the config or the wallpaper intended to be set cannot be found. Defaults to None. If none is set and the config has been written incorrectly, a ConfigError is raised and the program is exited. If an error in the config occurs but the fallback wallpaper has been defined, it will be set and wallman will exit with Code 1. If The config is written correctly but the wallpaper intended to be set can&rsquo;t be found, wallman will set the fallback wallpaper and continue to try setting future wallpapers. Wallpaper to be set if an error is found in the config. Defaults to None. If none is set and the config is written incorrectly, a ConfigError is raised and the program is exited. If an error in the config occurs but the fallback wallpaper has been defined, it will be set and wallman will exit with Code 1.
- Optional: loglevel: string
Loglevel to be used by wallman. Defaults to INFO. Choices MUST be DEBUG, INFO, WARNING, ERROR or CRITICAL. Using any capitalization is valid, all caps is reccomended. Wallman will crash if a value is specified that is not one of the specified ones.
- Optional: systray: bool
This defaults to &ldquo;true&rdquo;. This enables support for a systray that has the features to re-set your wallpaper (Mostly useful if feh shits itself or if you want to set the correct wallpaper for a specific time of day after your device was suspended) without rerolling the wallpaper set used, a button to reroll and then re-set the wallpaper, as well as a Quit button. Disable this to save a very tiny amount of memory.
<a id="org2778aa9"></a> <a id="orgc425813"></a>
### changing<sub>times</sub> ### changing<sub>times</sub>
The changing<sub>times</sub> 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 &ldquo;XX:YY:ZZ&rdquo; 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 changing<sub>times</sub> 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 &ldquo;XX:YY:ZZ&rdquo; 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.
<a id="orgf21f2d8"></a> <a id="org9f47b06"></a>
### The other dictionaries ### The other dictionaries
@ -206,12 +162,12 @@ The other dictionaries must always have the names of the wallpaper sets from use
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. 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.
<a id="org8cbebae"></a> <a id="orgc26c243"></a>
# TODOs # TODOs
<a id="orgb43553d"></a> <a id="org8dd22d4"></a>
## Structuring ## Structuring
@ -219,19 +175,21 @@ The keys in the dictionary once again do not matter, the names of the keys in ea
- Add documentation for developers - Add documentation for developers
<a id="org0a633fb"></a> <a id="org6103451"></a>
## Technical Details ## Technical Details
- Improve Modularity (Partially done) - Improve Modularity
- Make the enabled flag in wallpaper<sub>sets</sub> actually useful by making the used<sub>sets</sub> field optional - Make the enabled flag in wallpaper<sub>sets</sub> actually useful by making the used<sub>sets</sub> field optional
- 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 - Drop the feh dependecy and set wallpapers using pywlroots or python-xlib
<a id="org1c5725d"></a> <a id="org38d78ba"></a>
## Features ## Features
- Add support for setting a fallback wallpaper if a wallpaper the user set is not found
- 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 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 wallpapers that change by the weather
- Add support for live wallpapers - Add support for live wallpapers

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "wallman" name = "wallman"
version = "1.4.2.4" version = "1.3.0b1"
authors = [ authors = [
{name = "Emma Nora Theuer", email = "wallman@entheuer.de"}, {name = "Emma Nora Theuer", email = "wallman@entheuer.de"},
] ]
@ -24,8 +24,6 @@ classifiers = [
] ]
dependencies = [ dependencies = [
"APScheduler", "APScheduler",
"pillow",
"pystray",
'importlib-metadata; python_version<"3.10"', 'importlib-metadata; python_version<"3.10"',
] ]

View file

@ -6,8 +6,6 @@ used_sets = ["anime", "nature"]
wallpapers_per_set = 5 wallpapers_per_set = 5
notify = False notify = False
fallback_wallpaper = "/path/to/paper" fallback_wallpaper = "/path/to/paper"
loglevel = "INFO"
systray = true
# Enter the hours at which you want the wallpaper to change. # 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. # If the script is called between one of this times, it will go back to the previous time.

View file

@ -6,7 +6,7 @@ def main():
logic = wallman_lib.WallpaperLogic() logic = wallman_lib.WallpaperLogic()
validator.validate_config() validator.validate_config()
logic.set_wallpaper_by_time() logic.set_wallpaper_by_time()
logic.run_wallpapers() logic.schedule_wallpapers()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View file

@ -1,18 +1,33 @@
#!/usr/bin/env python3
from sys import exit from sys import exit
from os import chdir, getenv, system from os import chdir, getenv, system
import logging import logging
import tomllib import tomllib
from datetime import datetime, time from datetime import datetime, time
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.cron import CronTrigger
# Setup Logging. NOTE: Declaration as a global variable is necessary to ensure correct functionality across multiple modules. # setup logging
logger = logging.getLogger("wallman") chdir(str(getenv("HOME")) + "/.local/share/wallman/")
logger = logging.getLogger(__name__)
logging.basicConfig(filename="wallman.log", encoding="utf-8", level=logging.WARNING)
# read config
# a = list(data["changing_times"].values())
# print(a[0])
class ConfigError(Exception): class ConfigError(Exception):
pass pass
class _ConfigLib: class _ConfigLib:
# Initializes the most important config values. TODO: Add handling for the empty config case 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): def __init__(self):
self.config_file: dict = self._initialize_config() # Full config self.config_file: dict = self._initialize_config() # Full config
# Dictionaries # Dictionaries
@ -23,46 +38,11 @@ class _ConfigLib:
self.config_used_sets: list = self.config_general["used_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_wallpapers_per_set: int = self.config_general["wallpapers_per_set"]
self.config_total_changing_times: int = len(self.config_changing_times) self.config_total_changing_times: int = len(self.config_changing_times)
self.config_log_level: str = self.config_general.get("loglevel", "INFO").upper()
# HACK: Add a function to handle these try/except blocks cleanlier.
try: try:
self.config_notify: bool = self.config_general["notify"] self.config_notify = self.config_general["notify"]
except KeyError: except KeyError:
self.config_notify: bool = False self.config_notify = False
logger.warning("'notify' is not set in dictionary general in the config file, defaulting to 'false'.") logger.warning("'notify' is not set in dictionary general in the config file, defaulting to 'false'.")
try:
self.config_systray = self.config_general["systray"]
except KeyError:
self.config_systray = True
logger.warning("'systray' is not set in the dictionary general in the config file, defaulting to 'true'.")
# Setup logging
self._set_log_level()
# Setup systray.
if self.config_systray:
self._initialize_systray()
# Read config. TODO: Add error handling for the config not found case.
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
# HACK on this to avoid double importing of wallman_systray due to variable scope. Idea: Global variable or Variable that is inherited?
def _initialize_systray(self):
try:
import wallman_systray
except (ImportError, FileNotFoundError):
self.config_systray = False
def _set_log_level(self):
global logging
global logger
chdir("/var/log/wallman/")
numeric_level = getattr(logging, self.config_log_level, logging.INFO)
logger.setLevel(numeric_level)
logging.basicConfig(filename="wallman.log", encoding="utf-8", level=numeric_level)
def _set_fallback_wallpaper(self): def _set_fallback_wallpaper(self):
if self.config_general["fallback_wallpaper"]: if self.config_general["fallback_wallpaper"]:
@ -73,121 +53,94 @@ class _ConfigLib:
raise ConfigError("An error occured and no fallback wallpaper has been set, exiting...") raise ConfigError("An error occured and no fallback wallpaper has been set, exiting...")
class ConfigValidity(_ConfigLib): class ConfigValidity(_ConfigLib):
# TODO: Add handling for the empty config case.
def __init__(self): def __init__(self):
super().__init__() super().__init__()
def _check_fallback_wallpaper(self): def _check_fallback_wallpaper(self):
if self.config_general["fallback_wallpaper"]: if self.config_general["fallback_wallpaper"]:
logger.debug("A fallback wallpaper has been defined.") logger.debug("A fallback wallpaper has been defined.")
return True
else: else:
logger.warning("No fallback wallpaper has been provided. If the config is written incorrectly, the program will not be able to be executed.") logger.warning("No fallback wallpaper has been provided. If the config is written incorrectly, the program will not be able to be executed.")
return False
def _check_wallpapers_per_set_and_changing_times(self) -> bool: def _check_wallpapers_per_set_and_changing_times(self) -> None:
# Check if the amount of wallpapers_per_set and given changing times match # Check if the amount of wallpapers_per_set and given changing times match
if self.config_total_changing_times == self.config_wallpapers_per_set: 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") logger.debug("The amount of changing times and wallpapers per set is set correctly")
return True
else: else:
try: try:
self._set_fallback_wallpaper() self._set_fallback_wallpaper()
logger.error("The amount of changing_times and the amount of wallpapers_per_set does not match, the fallback wallpaper has been set.") 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 match, 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.")
return False exit(1)
except ConfigError: except ConfigError:
logger.critical("The amount of changing times and the amount of wallpapers per set does not match, exiting...") 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...") raise ConfigError("Please provide an amount of changing_times equal to wallpapers_per_set, exiting...")
def _check_general_validity(self) -> bool: def _check_general_validity(self) -> None:
# FIXME!
# HACK: Adjust it to check for the actually required variables existing rather than check if a number of options is set, which is highly error prone.
if len(self.config_general) < 3: if len(self.config_general) < 3:
try: try:
self._set_fallback_wallpaper() self._set_fallback_wallpaper()
logger.error("An insufficient amount of elements has been provided for general, the fallback wallpaper has been set.") 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.") print("ERROR: An insufficient amount of wallpapers has been provided for general, the fallback wallpaper has been set.")
return False exit(1)
except ConfigError: except ConfigError:
logger.critical("An insufficient amount of elements for general has been provided, exiting...") logger.critical("An insufficient amount of elements for general has been provided, exiting...")
raise ConfigError("general should have at least 3 elements, exiting...") raise ConfigError("general should have at least 3 elements, exiting...")
else: def _check_wallpaper_dicts(self)-> None:
logger.debug("A valid amount of options has been provided in general")
return True
def _check_wallpaper_dicts(self):
# This block checks if a dictionary for each wallpaper set exists # This block checks if a dictionary for each wallpaper set exists
for wallpaper_set in self.config_used_sets: for wallpaper_set in self.config_used_sets:
if wallpaper_set in self.config_file: if wallpaper_set in self.config_file:
logger.debug(f"The dictionary {wallpaper_set} has been found in config.") logger.debug(f"The dictionary {wallpaper_set} has been found in config.")
return True
# TODO split this into smaller pieces. This goes too deep.
else: else:
try: try:
self._set_fallback_wallpaper() self._set_fallback_wallpaper()
logger.error(f"The dictionary {wallpaper_set} has not been found in the config, the fallback wallpaper has been set.") 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.") print(f"ERROR: The dictionary {wallpaper_set} has not been found in the config, the fallback wallpaper has been set.")
return False exit(1)
except ConfigError: except ConfigError:
logger.critical(f"No dictionary {wallpaper_set} has been found in the config exiting...") 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...") raise ConfigError(f"The dictionary {wallpaper_set} has not been found in the config, exiting...")
def _check_wallpaper_amount(self): def _check_wallpaper_amount(self) -> None:
# This block checks if if each wallpaper set dictionary provides enough wallpapers to satisfy wallpapers_per_set # 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: for wallpaper_set in self.config_used_sets:
if len(self.config_file[wallpaper_set]) == self.config_wallpapers_per_set: if len(self.config_file[wallpaper_set]) == self.config_wallpapers_per_set:
logger.debug(f"Dictionary {wallpaper_set} has sufficient values.") logger.debug(f"Dictionary {wallpaper_set} has sufficient values.")
return True
else: else:
try: try:
self._set_fallback_wallpaper() self._set_fallback_wallpaper()
logger.error(f"The Dictionary {wallpaper_set} does not have sufficient entries, the fallback wallpaper has been set.") 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.") print(f"ERROR: The Dictionaty {wallpaper_set} does not have sufficient entries, the fallback wallpaper has been set.")
return False exit(1)
except ConfigError: except ConfigError:
logger.critical(f"Dictionary {wallpaper_set} does not have sufficient entries, exciting...") 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...") raise ConfigError(f"Dictionary {wallpaper_set} does not have the correct amount of entries, exciting...")
def validate_config(self) -> bool: def validate_config(self) -> None:
# NOTE: Consider changing this to exit(-1) self._check_fallback_wallpaper()
# HACK: Consider using different exit codes for different errors to help users debug. self._check_wallpapers_per_set_and_changing_times()
if not self._check_fallback_wallpaper(): self._check_general_validity()
pass self._check_wallpaper_dicts()
if not self._check_wallpapers_per_set_and_changing_times(): self._check_wallpaper_amount()
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)
logger.debug("The config file has been validated successfully (No Errors)") logger.debug("The config file has been validated successfully (No Errors)")
return True
# TODO: Improve modularity. See notes inside the class for more details.
# TODO: Ensure functionality and if needed add handling for the 1 wallpaper per set case.
class WallpaperLogic(_ConfigLib): class WallpaperLogic(_ConfigLib):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
# NOTE: This looks a bit ugly. Consider pros and cons of adding this into _ConfigLib
self.chosen_wallpaper_set = False self.chosen_wallpaper_set = False
# NOTE: This function could be in a different file because it's not needed in the case only 1 wallpaper per set is needed.
# Returns a list of a split string that contains a changing time from the config file # Returns a list of a split string that contains a changing time from the config file
def _clean_times(self, desired_time) -> list: def _clean_times(self, desired_time) -> list:
unclean_times = list(self.config_changing_times.values())[desired_time] unclean_times = list(self.config_changing_times.values())[desired_time]
return unclean_times.split(":") return unclean_times.split(":")
# NOTE: This could be in a different file because it's not needed in the "Only one wallpaper set" case.
def _choose_wallpaper_set(self) -> None: def _choose_wallpaper_set(self) -> None:
from random import choice as choose_from from random import choice as choose_from
self.chosen_wallpaper_set = choose_from(self.config_used_sets) self.chosen_wallpaper_set = choose_from(self.config_used_sets)
self.wallpaper_list = list(self.config_file[self.chosen_wallpaper_set].values()) self.wallpaper_list = list(self.config_file[self.chosen_wallpaper_set].values())
logger.debug(f"Chose wallpaper set {self.chosen_wallpaper_set}") logger.debug(f"Chose wallpaper set {self.chosen_wallpaper_set}")
# NOTE: Same as _clean_times()
# Verify if a given time is in a given range # Verify if a given time is in a given range
def _time_in_range(self, start, end, x) -> bool: def _time_in_range(self, start, end, x) -> bool:
if start <= end: if start <= end:
@ -195,96 +148,35 @@ class WallpaperLogic(_ConfigLib):
else: else:
return start <= x or x < end return start <= x or x < end
# NOTE: Potentially add handling for this to be also usable for notify_user and add logging if notify_user fails. Consider adding an argument that is where it's called from and handle accordingly.
def _check_system_exitcode(self, code) -> bool:
if code != 0:
try:
self._set_fallback_wallpaper()
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.")
return False
except ConfigError:
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.")
return False
else:
logger.info(f"The wallpaper {self.wallpaper_list[self.current_time_range]} has been set.")
return True
# NOTE: Add error handling in case libnotify is not installed or notify-send fails for any other reason.
# TODO: Add a check whether config[notify] is true or not.
def _notify_user(self): def _notify_user(self):
system("notify-send 'Wallman' 'A new Wallpaper has been set.'") system("notify-send 'Wallman' 'A new Wallpaper has been set.'")
logger.debug("Sent desktop notification.") logger.debug("Sent desktop notification.")
# TODO: Clean this up. It's way too large and way too intimidating. def set_wallpaper_by_time(self) -> None:
# NOTE: This could be in a different for the case that the user only wants 1 wallpaper per set.
# TODO: Add an way for the user to choose if the wallpaper should scale, fill or otherwise. This needs to be editable in the config file.
def set_wallpaper_by_time(self) -> bool:
# Ensure use of a consistent wallpaper set # Ensure use of a consistent wallpaper set
if self.chosen_wallpaper_set is False: if self.chosen_wallpaper_set is False:
self._choose_wallpaper_set() self._choose_wallpaper_set()
for time_range in range(self.config_total_changing_times - 1): for time_range in range(self.config_total_changing_times - 1):
self.current_time_range = time_range # Store current time for better debugging output
clean_time = self._clean_times(time_range) clean_time = self._clean_times(time_range)
clean_time_two = self._clean_times(time_range + 1) clean_time_two = self._clean_times(time_range + 1)
# HACK on this to make it more readable. This function call is way too long. Consider storing these in a bunch of temporary variables, though keep function length in mind.
# HACK on this to see if this logic can be simplified. It's very ugly to check it that way.
# Check if the current time is between a given and the following changing time and if so, set that wallpaper. If not, keep trying. # 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()): 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 --quiet {self.wallpaper_list[time_range]}") system(f"feh --bg-scale --no-fehbg {self.wallpaper_list[time_range]}")
exitcode = system(f"feh --bg-scale --no-fehbg --quiet {self.wallpaper_list[time_range]}")
has_wallpaper_been_set = self._check_system_exitcode(exitcode)
# TODO: Add this check to _notify_user.
if self.config_notify: if self.config_notify:
self._notify_user() self._notify_user()
return has_wallpaper_been_set return
else: else:
continue continue
system(f"feh --bg-scale --no-fehbg {self.wallpaper_list[-1]}") system(f"feh --bg-scale --no-fehbg {self.wallpaper_list[-1]}")
exitcode = system(f"feh --bg-scale --no-fehbg {self.wallpaper_list[-1]}")
has_wallpaper_been_set = self._check_system_exitcode(exitcode)
if self.config_notify: if self.config_notify:
self._notify_user() self._notify_user()
return has_wallpaper_been_set
# NOTE: Consider avoiding nested functions.
def schedule_wallpapers(self): def schedule_wallpapers(self):
def _schedule_background_wallpapers(): scheduler = BlockingScheduler()
from apscheduler.schedulers.background import BackgroundScheduler # Create a scheduled job for every changing time
scheduler = BackgroundScheduler() for changing_time in range(len(self.config_changing_times)):
# Create a scheduled job for every changing time clean_time = self._clean_times(changing_time)
# NOTE: This should be a function. scheduler.add_job(self.set_wallpaper_by_time, trigger=CronTrigger(hour=clean_time[0], minute=clean_time[1], second=clean_time[2]))
for changing_time in range(len(self.config_changing_times)): scheduler.start()
clean_time = self._clean_times(changing_time) logger.info("The scheduler has been started.")
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 background scheduler has been started.")
return scheduler
def _schedule_blocking_wallpapers():
from apscheduler.schedulers.blocking import BlockingScheduler
scheduler = BlockingScheduler()
# Create a scheduled job for every changing time
# NOTE: Thisshould be a function.
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]))
logger.info("The blocking scheduler has been started.")
scheduler.start()
if self.config_systray:
# NOTE: The wallman_systray impomrt should be handled differently. See the note in Config_Validity.
import wallman_systray as systray
from functools import partial
scheduler = _schedule_background_wallpapers()
menu = systray.Menu (
systray.item("Re-Set Wallpaper", partial(systray.set_wallpaper_again, wallpaper_setter=self.set_wallpaper_by_time)),
systray.item("Reroll Wallpapers", partial(systray.reroll_wallpapers, wallpaper_chooser=self._choose_wallpaper_set, wallpaper_setter=self.set_wallpaper_by_time)),
systray.item("Quit", partial(systray.on_quit, shutdown_scheduler=scheduler.shutdown))
)
icon = systray.Icon("wallman_icon", systray.icon_image, "My Tray Icon", menu)
icon.run()
else:
_schedule_blocking_wallpapers()

View file

@ -1,42 +0,0 @@
from os import getenv, chdir
import logging
# Use logger that is also in wallman_lib
logger = logging.getLogger("wallman")
try:
from PIL import Image
except ImportError:
logging.error("Couldn't import PIL, wallman will launch without a systray.")
print("Couldn't import PIL, wallman will launch without a systray.")
raise
try:
from pystray import Icon, MenuItem as item, Menu
except ImportError:
logging.error("Couldn't import pystray, wallman will launch without a systray.")
print("Couldn't import pystray, wallman will launch without a systray.")
raise
# This should always be ran with "set_wallpaper_by_time" as input!
def set_wallpaper_again(icon, item, wallpaper_setter):
logging.info("Re-Setting wallpaper due to systray input.")
wallpaper_setter()
def reroll_wallpapers(icon, item, wallpaper_chooser, wallpaper_setter):
logging.info("Rerolling Wallpaper sets and resetting wallpaper due to systray input")
wallpaper_chooser()
wallpaper_setter()
# This should always be ran with "scheduler.shutdown" as input!
def on_quit(icon, item, shutdown_scheduler):
logging.info("Shutting down wallman due to systray input.")
shutdown_scheduler()
icon.stop()
chdir("/etc/wallman/icons/")
try:
icon_image = Image.open("systrayIcon.jpg")
except FileNotFoundError:
logger.error("~/.config/wallman/systrayIcon.jpg has not been found, wallman will launch without a systray.")
print("~/.config/wallman/systrayIcon.jpg has not been found, wallman will launch without a systray.")