трансфер с гитхаба
This commit is contained in:
+38
-7
@@ -1,7 +1,6 @@
|
|||||||
# ---> Python
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[codz]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
@@ -47,7 +46,7 @@ htmlcov/
|
|||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*.cover
|
*.cover
|
||||||
*.py,cover
|
*.py.cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
cover/
|
cover/
|
||||||
@@ -107,17 +106,24 @@ ipython_config.py
|
|||||||
# commonly ignored for libraries.
|
# commonly ignored for libraries.
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
#poetry.lock
|
#poetry.lock
|
||||||
|
#poetry.toml
|
||||||
|
|
||||||
# pdm
|
# pdm
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||||
|
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||||
#pdm.lock
|
#pdm.lock
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
#pdm.toml
|
||||||
# in version control.
|
|
||||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
||||||
.pdm.toml
|
|
||||||
.pdm-python
|
.pdm-python
|
||||||
.pdm-build/
|
.pdm-build/
|
||||||
|
|
||||||
|
# pixi
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||||
|
#pixi.lock
|
||||||
|
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||||
|
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||||
|
.pixi
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
__pypackages__/
|
__pypackages__/
|
||||||
|
|
||||||
@@ -130,6 +136,7 @@ celerybeat.pid
|
|||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
.env
|
.env
|
||||||
|
.envrc
|
||||||
.venv
|
.venv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
@@ -168,9 +175,33 @@ cython_debug/
|
|||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
# Abstra
|
||||||
|
# Abstra is an AI-powered process automation framework.
|
||||||
|
# Ignore directories containing user credentials, local state, and settings.
|
||||||
|
# Learn more at https://abstra.io/docs
|
||||||
|
.abstra/
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||||
|
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||||
|
# you could uncomment the following to ignore the entire vscode folder
|
||||||
|
# .vscode/
|
||||||
|
|
||||||
# Ruff stuff:
|
# Ruff stuff:
|
||||||
.ruff_cache/
|
.ruff_cache/
|
||||||
|
|
||||||
# PyPI configuration file
|
# PyPI configuration file
|
||||||
.pypirc
|
.pypirc
|
||||||
|
|
||||||
|
# Cursor
|
||||||
|
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
||||||
|
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
||||||
|
# refer to https://docs.cursor.com/context/ignore-files
|
||||||
|
.cursorignore
|
||||||
|
.cursorindexingignore
|
||||||
|
|
||||||
|
# Marimo
|
||||||
|
marimo/_static/
|
||||||
|
marimo/_lsp/
|
||||||
|
__marimo__/
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 s13bby
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,3 +1,253 @@
|
|||||||
# wallpaper_demon
|
# 🖼️ Wallpaper Daemon
|
||||||
|
|
||||||
Служба для автоматической смены обоев рабочего стола в зависимости от времени суток.
|
Служба для автоматической смены обоев рабочего стола в зависимости от времени суток.
|
||||||
|
|
||||||
|
## 📋 Возможности
|
||||||
|
|
||||||
|
- 🌅 **Автоматическая смена обоев** — установка разных обоев для разных периодов дня
|
||||||
|
- 🎨 **Смена темы GNOME** — автоматическое переключение светлой/тёмной темы
|
||||||
|
- ⏰ **Гибкое расписание** — настройка времени смены обоев через JSON
|
||||||
|
- 🔧 **Systemd-служба** — интеграция с systemd для автоматического запуска
|
||||||
|
- 📝 **Логирование** — запись всех событий в лог
|
||||||
|
|
||||||
|
## 🚀 Требования
|
||||||
|
|
||||||
|
- Python 3.13+
|
||||||
|
- Linux с GNOME и systemd
|
||||||
|
- Установленные пакеты:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install python3-pip python3-venv rsync
|
||||||
|
```
|
||||||
|
|
||||||
|
- Библиотеки Python (стандартные, установка не требуется):
|
||||||
|
|
||||||
|
```
|
||||||
|
subprocess, os, json, sys, signal, logging, datetime
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📖 Использование
|
||||||
|
|
||||||
|
### Быстрый старт
|
||||||
|
|
||||||
|
**Однократная установка обоев:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python wallpaper_handle.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**Запуск демона (вручную):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python wallpaper_daemon.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Управление через systemd
|
||||||
|
|
||||||
|
| Действие | Команда |
|
||||||
|
|----------|---------|
|
||||||
|
| Установка службы | `sudo ./install_service.sh` |
|
||||||
|
| Включение службы | `systemctl --user enable wallpaper-daemon.service` |
|
||||||
|
| Запуск службы | `systemctl --user start wallpaper-daemon.service` |
|
||||||
|
| Остановка службы | `systemctl --user stop wallpaper-daemon.service` |
|
||||||
|
| Проверка статуса | `systemctl --user status wallpaper-daemon.service` |
|
||||||
|
| Удаление службы | `sudo ./uninstall_service.sh` |
|
||||||
|
|
||||||
|
## ⏰ Расписание смены обоев
|
||||||
|
|
||||||
|
| Время суток | Период | Обои | Тема |
|
||||||
|
|-------------|--------|------|------|
|
||||||
|
| 🌙 Ночь | 00:00 - 07:59 | `night.jpg` | 🌑 Тёмная |
|
||||||
|
| 🌅 Утро | 08:00 - 11:59 | `morning.jpg` | ☀️ Светлая |
|
||||||
|
| ☀️ День | 12:00 - 15:59 | `day.jpg` | ☀️ Светлая |
|
||||||
|
| 🌆 Вечер | 16:00 - 19:59 | `evening.jpg` | ☀️ Светлая |
|
||||||
|
| 🌃 Перед ночью | 20:00 - 23:59 | `before_night.jpg` | 🌑 Тёмная |
|
||||||
|
|
||||||
|
## ⚙️ Настройки
|
||||||
|
|
||||||
|
Файл `settings.json` в корне проекта:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"night": {
|
||||||
|
"time": ["0:0", "7:59"],
|
||||||
|
"theme": "dark",
|
||||||
|
"file": "night.jpg"
|
||||||
|
},
|
||||||
|
"morning": {
|
||||||
|
"time": ["8:0", "11:59"],
|
||||||
|
"theme": "light",
|
||||||
|
"file": "morning.jpg"
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"time": ["12:0", "15:59"],
|
||||||
|
"theme": "light",
|
||||||
|
"file": "day.jpg"
|
||||||
|
},
|
||||||
|
"evening": {
|
||||||
|
"time": ["16:0", "19:59"],
|
||||||
|
"theme": "light",
|
||||||
|
"file": "evening.jpg"
|
||||||
|
},
|
||||||
|
"before_night": {
|
||||||
|
"time": ["20:0", "23:59"],
|
||||||
|
"theme": "dark",
|
||||||
|
"file": "before_night.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Параметры
|
||||||
|
|
||||||
|
| Параметр | Описание |
|
||||||
|
|----------|----------|
|
||||||
|
| `time` | Период времени в формате `["часы:минуты", "часы:минуты"]` |
|
||||||
|
| `theme` | Тема GNOME: `light` или `dark` |
|
||||||
|
| `file` | Имя файла обоев в папке `wallpapers/` |
|
||||||
|
|
||||||
|
## 📂 Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
wallpaper-daemon/
|
||||||
|
├── wallpaper_daemon.py # Основной демон (фоновый режим)
|
||||||
|
├── wallpaper_handle.py # Однократная установка обоев
|
||||||
|
├── settings.json # Конфигурация расписания
|
||||||
|
├── install_service.sh # Скрипт установки systemd-службы
|
||||||
|
├── uninstall_service.sh # Скрипт удаления systemd-службы
|
||||||
|
├── wallpaper-daemon.service # Unit-файл systemd
|
||||||
|
├── README.md
|
||||||
|
├── LICENSE
|
||||||
|
├── .gitignore
|
||||||
|
└── wallpapers/
|
||||||
|
├── morning.jpg # Утренние обои
|
||||||
|
├── day.jpg # Дневные обои
|
||||||
|
├── evening.jpg # Вечерние обои
|
||||||
|
├── before_night.jpg # Обои перед ночью
|
||||||
|
└── night.jpg # Ночные обои
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Установка службы
|
||||||
|
|
||||||
|
### Автоматическая установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./install_service.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт:
|
||||||
|
1. Копирует файлы в `~/.local/share/wallpaper-daemon`
|
||||||
|
2. Устанавливает службу в `/etc/systemd/user`
|
||||||
|
3. Включает linger для пользователя
|
||||||
|
|
||||||
|
> **Примечание:** После установки службы нужно включить и запустить её от имени пользователя:
|
||||||
|
> ```bash
|
||||||
|
> systemctl --user enable wallpaper-daemon.service
|
||||||
|
> systemctl --user start wallpaper-daemon.service
|
||||||
|
> ```
|
||||||
|
|
||||||
|
### Переустановка
|
||||||
|
|
||||||
|
Если служба была установлена неправильно (например, в `/root/.local/...`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Удалить старую службу
|
||||||
|
sudo ./uninstall_service.sh
|
||||||
|
|
||||||
|
# Установить заново
|
||||||
|
sudo ./install_service.sh
|
||||||
|
|
||||||
|
# Включить и запустить
|
||||||
|
systemctl --user enable wallpaper-daemon.service
|
||||||
|
systemctl --user start wallpaper-daemon.service
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ручная установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Создать директорию
|
||||||
|
mkdir -p ~/.local/share/wallpaper-daemon
|
||||||
|
|
||||||
|
# Скопировать файлы (без скриптов установки)
|
||||||
|
rsync -av --exclude='install_service.sh' --exclude='uninstall_service.sh' ./ ~/.local/share/wallpaper-daemon/
|
||||||
|
|
||||||
|
# Скопировать unit-файл
|
||||||
|
sudo cp wallpaper-daemon.service /etc/systemd/user/
|
||||||
|
|
||||||
|
# Перезагрузить демон
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
|
# Включить linger
|
||||||
|
loginctl enable-linger $USER
|
||||||
|
|
||||||
|
# Включить и запустить службу
|
||||||
|
systemctl --user enable wallpaper-daemon.service
|
||||||
|
systemctl --user start wallpaper-daemon.service
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ Устранение неполадок
|
||||||
|
|
||||||
|
### Служба не запускается
|
||||||
|
|
||||||
|
**Проверьте статус:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user status wallpaper-daemon.service
|
||||||
|
```
|
||||||
|
|
||||||
|
**Посмотрите детальные логи:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
journalctl --user -u wallpaper-daemon.service -n 100 --no-pager
|
||||||
|
```
|
||||||
|
|
||||||
|
**Просмотр логов в реальном времени:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
journalctl --user -u wallpaper-daemon.service -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Обои не меняются
|
||||||
|
|
||||||
|
1. **Проверьте наличие файлов:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -la ~/.local/share/wallpaper-daemon/wallpapers/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Убедитесь, что GNOME активен:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gsettings get org.gnome.desktop.background picture-uri
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Проверьте переменные окружения:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo $DISPLAY
|
||||||
|
echo $DBUS_SESSION_BUS_ADDRESS
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ошибки в логах
|
||||||
|
|
||||||
|
| Ошибка | Решение |
|
||||||
|
|--------|---------|
|
||||||
|
| `Файл не найден` | Проверьте наличие обоев в папке `wallpapers/` |
|
||||||
|
| `gsettings: не удалось открыть` | Убедитесь, что GNOME запущен |
|
||||||
|
| `Permission denied` | Проверьте права доступа к файлам |
|
||||||
|
|
||||||
|
## 🎨 Добавление своих обоев
|
||||||
|
|
||||||
|
1. Поместите изображения в папку `wallpapers/`
|
||||||
|
2. Отредактируйте `settings.json`:
|
||||||
|
- Измените `file` на имя вашего файла
|
||||||
|
- Настройте `time` для нужного периода
|
||||||
|
- Выберите `theme` (`light` или `dark`)
|
||||||
|
3. Перезапустите службу:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user restart wallpaper-daemon.service
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**сделано: s13bby**
|
||||||
|
|||||||
Executable
+50
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
SERVICE_NAME="wallpaper-daemon"
|
||||||
|
SERVICE_FILE="$SCRIPT_DIR/wallpaper-daemon.service"
|
||||||
|
SYSTEM_SERVICE_DIR="/etc/systemd/user"
|
||||||
|
|
||||||
|
# Используем домашнюю директорию реального пользователя, а не root
|
||||||
|
if [ "$EUID" -eq 0 ]; then
|
||||||
|
REAL_USER="${SUDO_USER:-$(logname)}"
|
||||||
|
INSTALL_DIR="/home/$REAL_USER/.local/share/wallpaper-daemon"
|
||||||
|
else
|
||||||
|
INSTALL_DIR="$HOME/.local/share/wallpaper-daemon"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Установка файлов в $INSTALL_DIR..."
|
||||||
|
mkdir -p "$INSTALL_DIR"
|
||||||
|
|
||||||
|
rsync -av --exclude='install_service.sh' --exclude='uninstall_service.sh' \
|
||||||
|
"$SCRIPT_DIR/" "$INSTALL_DIR/" 2>/dev/null || cp -r "$SCRIPT_DIR"/* "$INSTALL_DIR/"
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "Для установки сервиса требуются права root. Запустите с sudo:"
|
||||||
|
echo " sudo $0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$SYSTEM_SERVICE_DIR"
|
||||||
|
|
||||||
|
cp "$SERVICE_FILE" "$SYSTEM_SERVICE_DIR/$SERVICE_NAME.service"
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
loginctl enable-linger "$REAL_USER"
|
||||||
|
|
||||||
|
# Устанавливаем владельца файлов
|
||||||
|
chown -R "$REAL_USER:$REAL_USER" "$INSTALL_DIR"
|
||||||
|
|
||||||
|
echo "Сервис установлен в $SYSTEM_SERVICE_DIR"
|
||||||
|
echo "Файлы скопированы в $INSTALL_DIR"
|
||||||
|
echo ""
|
||||||
|
echo "Для запуска выполните:"
|
||||||
|
echo " systemctl --user enable $SERVICE_NAME.service"
|
||||||
|
echo " systemctl --user start $SERVICE_NAME.service"
|
||||||
|
echo ""
|
||||||
|
echo "Проверить статус:"
|
||||||
|
echo " systemctl --user status $SERVICE_NAME.service"
|
||||||
|
echo " journalctl --user -u $SERVICE_NAME.service -f"
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"night":
|
||||||
|
{
|
||||||
|
"time":["0:0", "7:59"],
|
||||||
|
"theme": "dark",
|
||||||
|
"file": "night.jpg"
|
||||||
|
},
|
||||||
|
"morning":
|
||||||
|
{
|
||||||
|
"time":["8:0", "11:59"],
|
||||||
|
"theme": "light",
|
||||||
|
"file": "morning.jpg"
|
||||||
|
},
|
||||||
|
"day":
|
||||||
|
{
|
||||||
|
"time": ["12:0", "15:59"],
|
||||||
|
"theme": "light",
|
||||||
|
"file": "day.jpg"
|
||||||
|
},
|
||||||
|
"evening":
|
||||||
|
{
|
||||||
|
"time": ["16:0", "19:59"],
|
||||||
|
"theme": "light",
|
||||||
|
"file": "evening.jpg"
|
||||||
|
},
|
||||||
|
"before_night":
|
||||||
|
{
|
||||||
|
"time": ["20:0", "23:59"],
|
||||||
|
"theme": "dark",
|
||||||
|
"file": "before_night.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SERVICE_NAME="wallpaper-daemon"
|
||||||
|
SYSTEM_SERVICE_DIR="/etc/systemd/user"
|
||||||
|
|
||||||
|
# Используем домашнюю директорию реального пользователя, а не root
|
||||||
|
if [ "$EUID" -eq 0 ]; then
|
||||||
|
REAL_USER="${SUDO_USER:-$(logname)}"
|
||||||
|
INSTALL_DIR="/home/$REAL_USER/.local/share/wallpaper-daemon"
|
||||||
|
else
|
||||||
|
INSTALL_DIR="$HOME/.local/share/wallpaper-daemon"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "Для удаления сервиса требуются права root. Запустите с sudo:"
|
||||||
|
echo " sudo $0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Остановка сервисов у активных пользователей..."
|
||||||
|
for uid in $(loginctl list-users --no-legend | awk '{print $1}'); do
|
||||||
|
user=$(id -un "$uid" 2>/dev/null)
|
||||||
|
if [ -n "$user" ]; then
|
||||||
|
echo " - $user"
|
||||||
|
runuser -u "$user" -- systemctl --user stop "$SERVICE_NAME.service" 2>/dev/null || true
|
||||||
|
runuser -u "$user" -- systemctl --user disable "$SERVICE_NAME.service" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for uid in $(loginctl list-users --no-legend | awk '{print $1}'); do
|
||||||
|
user=$(id -un "$uid" 2>/dev/null)
|
||||||
|
if [ -n "$user" ]; then
|
||||||
|
loginctl disable-linger "$user" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -f "$SYSTEM_SERVICE_DIR/$SERVICE_NAME.service"
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
echo "Удаление файлов из $INSTALL_DIR..."
|
||||||
|
rm -rf "$INSTALL_DIR"
|
||||||
|
|
||||||
|
echo "Сервис удален"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Wallpaper Auto-Switcher Daemon
|
||||||
|
After=graphical-session.target
|
||||||
|
PartOf=graphical-session.target
|
||||||
|
Wants=sleep.target suspend.target hibernate.target hybrid-sleep.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=%h/.local/share/wallpaper-daemon
|
||||||
|
ExecStart=%h/.local/share/wallpaper-daemon/venv/bin/python %h/.local/share/wallpaper-daemon/wallpaper_daemon.py
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
Environment=DISPLAY=:0
|
||||||
|
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%U/bus
|
||||||
|
|
||||||
|
IOSchedulingClass=idle
|
||||||
|
CPUSchedulingPolicy=idle
|
||||||
|
Nice=19
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=graphical-session.target
|
||||||
Executable
+177
@@ -0,0 +1,177 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[logging.StreamHandler()]
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
RUNNING = True
|
||||||
|
|
||||||
|
def signal_handler(signum, frame):
|
||||||
|
global RUNNING
|
||||||
|
RUNNING = False
|
||||||
|
logger.info("Получен сигнал остановки, завершаю работу...")
|
||||||
|
|
||||||
|
def get_base_dir():
|
||||||
|
return os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
def get_settings():
|
||||||
|
path = os.path.join(get_base_dir(), "settings.json")
|
||||||
|
try:
|
||||||
|
with open(path, "r", encoding="utf-8") as data:
|
||||||
|
return json.load(data)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка чтения {path}: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def get_current_time():
|
||||||
|
now = datetime.now()
|
||||||
|
return now.hour, now.minute, now.second
|
||||||
|
|
||||||
|
def parse_time(time_str):
|
||||||
|
h, m = map(int, time_str.split(':'))
|
||||||
|
return h * 60 + m
|
||||||
|
|
||||||
|
def choose_wallpaper(current_time, settings):
|
||||||
|
current_hours, current_minutes, _ = current_time
|
||||||
|
current_minutes_total = current_hours * 60 + current_minutes
|
||||||
|
|
||||||
|
for phase_name, data in settings.items():
|
||||||
|
start = parse_time(data["time"][0])
|
||||||
|
end = parse_time(data["time"][1])
|
||||||
|
|
||||||
|
if start <= end:
|
||||||
|
if start <= current_minutes_total <= end:
|
||||||
|
return data["file"], data["theme"], phase_name
|
||||||
|
else:
|
||||||
|
if current_minutes_total >= start or current_minutes_total <= end:
|
||||||
|
return data["file"], data["theme"], phase_name
|
||||||
|
|
||||||
|
return "night.jpg", "dark", "night"
|
||||||
|
|
||||||
|
def check_system_active():
|
||||||
|
"""Проверяет, активна ли система (не в процессе suspend/resume)"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["systemctl", "status", "sleep.target", "suspend.target",
|
||||||
|
"hibernate.target", "hybrid-sleep.target"],
|
||||||
|
capture_output=True, text=True, timeout=2
|
||||||
|
)
|
||||||
|
# Если есть активные таргеты сна - ждём
|
||||||
|
if result.returncode == 0:
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return True
|
||||||
|
|
||||||
|
def set_wallpaper(image):
|
||||||
|
wallpaper_path = os.path.join(get_base_dir(), "wallpapers", image)
|
||||||
|
|
||||||
|
if not os.path.exists(wallpaper_path):
|
||||||
|
logger.error(f"Файл не найден: {wallpaper_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
uri = f"file://{wallpaper_path}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["gsettings", "set", "org.gnome.desktop.background", "picture-options", "zoom"],
|
||||||
|
check=True, capture_output=True
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.warning(f"Ошибка picture-options: {e}")
|
||||||
|
|
||||||
|
keys = [
|
||||||
|
("org.gnome.desktop.background", "picture-uri"),
|
||||||
|
("org.gnome.desktop.background", "picture-uri-dark"),
|
||||||
|
]
|
||||||
|
|
||||||
|
success = True
|
||||||
|
for schema, setting in keys:
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["gsettings", "set", schema, setting, f"'{uri}'"],
|
||||||
|
check=True, capture_output=True
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.warning(f"Ошибка {setting}: {e}")
|
||||||
|
success = False
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
def switch_theme(theme):
|
||||||
|
scheme = "prefer-dark" if theme == "dark" else "prefer-light"
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", scheme],
|
||||||
|
check=True, capture_output=True
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.warning(f"Ошибка смены темы: {e}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global RUNNING
|
||||||
|
|
||||||
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
|
||||||
|
settings = get_settings()
|
||||||
|
last_applied = None
|
||||||
|
was_inactive = False
|
||||||
|
|
||||||
|
logger.info("Демон обоев запущен. Ожидание переключений...")
|
||||||
|
|
||||||
|
while RUNNING:
|
||||||
|
current_time = get_current_time()
|
||||||
|
image, theme, phase = choose_wallpaper(current_time, settings)
|
||||||
|
current_key = f"{image}_{theme}"
|
||||||
|
|
||||||
|
# Проверяем активность системы
|
||||||
|
system_active = check_system_active()
|
||||||
|
|
||||||
|
# Если система только что вернулась из сна - применяем обои принудительно
|
||||||
|
if was_inactive and system_active:
|
||||||
|
logger.info("Система вернулась из сна, применяю обои...")
|
||||||
|
switch_theme(theme)
|
||||||
|
if set_wallpaper(image):
|
||||||
|
last_applied = current_key
|
||||||
|
was_inactive = False
|
||||||
|
elif current_key != last_applied:
|
||||||
|
logger.info(f"Переключение: {phase} -> {image} (тема: {theme})")
|
||||||
|
switch_theme(theme)
|
||||||
|
if set_wallpaper(image):
|
||||||
|
last_applied = current_key
|
||||||
|
else:
|
||||||
|
logger.debug(f"Без изменений: {phase} -> {image}")
|
||||||
|
|
||||||
|
if not system_active:
|
||||||
|
was_inactive = True
|
||||||
|
logger.debug("Система неактивна (сон/приостановка)")
|
||||||
|
|
||||||
|
# Короткий интервал опроса для быстрой реакции после сна
|
||||||
|
# Используем interruptible sleep для корректной обработки сигналов
|
||||||
|
sleep_seconds = 30
|
||||||
|
|
||||||
|
logger.debug(f"Сон на {sleep_seconds} сек")
|
||||||
|
|
||||||
|
# Прерываемый сон - проверяем RUNNING каждые 0.5 сек
|
||||||
|
import time
|
||||||
|
for _ in range(sleep_seconds * 2):
|
||||||
|
if not RUNNING:
|
||||||
|
break
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
logger.info("Демон остановлен")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def get_settings():
|
||||||
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
path = os.path.join(base_dir, "settings.json")
|
||||||
|
try:
|
||||||
|
with open(path, "r", encoding="utf-8") as data:
|
||||||
|
return json.load(data)
|
||||||
|
except Exception:
|
||||||
|
print(f"Ошибка: Не удалось прочитать {path}")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
def get_current_time():
|
||||||
|
return [datetime.now().hour, datetime.now().minute]
|
||||||
|
|
||||||
|
def switch_theme(theme):
|
||||||
|
scheme = "prefer-dark" if theme == "dark" else "prefer-light"
|
||||||
|
cmd = ["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", scheme]
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Ошибка при смене темы: {e}")
|
||||||
|
|
||||||
|
def choose_wallpaper(current_time, settings):
|
||||||
|
current_hours, current_minutes = current_time
|
||||||
|
for phase_name, data in settings.items():
|
||||||
|
start_hours, start_minutes = map(int, data["time"][0].split(':'))
|
||||||
|
end_hours, end_minutes = map(int, data["time"][1].split(':'))
|
||||||
|
if (start_hours, start_minutes) <= (current_hours, current_minutes) \
|
||||||
|
<= (end_hours, end_minutes):
|
||||||
|
return data["file"], data["theme"]
|
||||||
|
return "night.jpg", "dark"
|
||||||
|
|
||||||
|
def main():
|
||||||
|
settings = get_settings()
|
||||||
|
current_time = get_current_time()
|
||||||
|
image, theme = choose_wallpaper(current_time, settings)
|
||||||
|
switch_theme(theme)
|
||||||
|
set_wallpaper(image)
|
||||||
|
|
||||||
|
|
||||||
|
def set_wallpaper(image):
|
||||||
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
wallpaper_path = os.path.join(base_dir, "wallpapers", image)
|
||||||
|
|
||||||
|
if not os.path.exists(wallpaper_path):
|
||||||
|
print(f"ОШИБКА: Файл не найден: {wallpaper_path}")
|
||||||
|
return
|
||||||
|
uri = f"file://{wallpaper_path}"
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["gsettings", "set", "org.gnome.desktop.background", "picture-options", "zoom"],
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Ошибка при установке picture-options: {e}")
|
||||||
|
|
||||||
|
keys = [
|
||||||
|
("org.gnome.desktop.background", "picture-uri"),
|
||||||
|
("org.gnome.desktop.background", "picture-uri-dark"),
|
||||||
|
("org.gnome.desktop.screensaver", "picture-uri")
|
||||||
|
]
|
||||||
|
|
||||||
|
for schema, setting in keys:
|
||||||
|
command = ["gsettings", "set", schema, setting, f"'{uri}'"]
|
||||||
|
try:
|
||||||
|
subprocess.run(command, check=True)
|
||||||
|
print(f"Успешно: {setting} -> {image}")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Ошибка: {e}")
|
||||||
|
try:
|
||||||
|
subprocess.run([
|
||||||
|
"gdbus", "call", "--session",
|
||||||
|
"--dest", "org.gnome.Shell",
|
||||||
|
"--object-path", "/org/gnome/Shell",
|
||||||
|
"--method", "org.gnome.Shell.Eval",
|
||||||
|
"imports.gi.Meta.BackgroundActor.get_for_screen(global.display.get_screen()).force_redraw()"
|
||||||
|
], check=False, capture_output=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
Reference in New Issue
Block a user