трансфер с гитхаба
This commit is contained in:
+38
-7
@@ -1,7 +1,6 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.py[codz]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
@@ -47,7 +46,7 @@ htmlcov/
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
*.py.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
@@ -107,17 +106,24 @@ ipython_config.py
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
#poetry.toml
|
||||
|
||||
# pdm
|
||||
# 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 stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
#pdm.toml
|
||||
.pdm-python
|
||||
.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
|
||||
__pypackages__/
|
||||
|
||||
@@ -130,6 +136,7 @@ celerybeat.pid
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.envrc
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
@@ -168,9 +175,33 @@ cython_debug/
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.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_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.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