Первый выпуск SysDevCleaner

Анализ Windows и очистка кешей, мусора и остатков разработки.

Ядро:
- каталог правил: 228 правил путей (496 шаблонов) + 43 правила артефактов
  проектов — Windows, браузеры, кеши сборки, IDE, приложения, пакетные
  менеджеры;
- рекурсивный поиск node_modules, target, build, __pycache__, .venv и т.п.
  с проверкой маркеров проекта, чтобы не ловить ложные срабатывания;
- поиск крупных файлов и дубликатов (хеш по началу/середине/концу и полная
  сверка), инвентаризация установленных программ через реестр;
- ограничение области сканирования по дискам;
- рекомендации и текстовый отчёт.

Безопасность:
- четыре уровня риска, автоматически отмечается только «Безопасно»;
- список защищённых путей проверяется и при выборе, и перед удалением;
- для каталогов вроде %TEMP% удаляется содержимое, а не сам каталог;
- удаление в Корзину по умолчанию, пробный запуск, журнал очисток.

Интерфейс на Dear PyGui:
- JetBrains Mono Nerd Font: кириллица и иконки в одном шрифте;
- тёмная и светлая темы, адаптивная раскладка;
- заголовок окна ставится через SetWindowTextW — Dear PyGui отдаёт его
  в ANSI-функцию как UTF-8 и портит кириллицу;
- запуск всегда с правами администратора: манифест UAC в exe и
  самоперезапуск через ShellExecuteW при работе из исходников.

Сборка в один exe через PyInstaller со вшитым шрифтом и значком.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 14:55:00 +03:00
commit b01cf37e66
33 changed files with 6472 additions and 0 deletions

144
sysdevcleaner/winui.py Normal file
View File

@@ -0,0 +1,144 @@
"""Работа с нативным окном Windows и повышение прав.
Здесь две вещи, которые Dear PyGui сам не умеет:
* **Заголовок окна.** ``dpg.create_viewport(title=...)`` отдаёт строку в ANSI-функцию
Windows как UTF-8 байты, поэтому русский заголовок превращается в
«SysDevCleaner вЂ" анализ...». Лечится переустановкой заголовка через
``SetWindowTextW`` — она принимает UTF-16 и показывает текст правильно.
* **Права администратора.** Без них недоступны ``Windows\\Temp``, кеш обновлений,
Prefetch, ``Windows.old`` и ещё несколько системных каталогов.
"""
from __future__ import annotations
import ctypes
import os
import sys
IS_WIN = sys.platform == "win32"
# Служебный ASCII-заголовок: ставим его в create_viewport, чтобы гарантированно
# найти окно по имени, а потом заменяем на настоящий.
BOOT_TITLE = "SysDevCleaner (starting)"
# Переменная окружения-предохранитель от бесконечного перезапуска.
NO_ELEVATE_ENV = "SYSDEVCLEAN_NO_ELEVATE"
def is_frozen() -> bool:
"""Запущены ли мы из собранного PyInstaller exe."""
return bool(getattr(sys, "frozen", False))
def icon_path() -> str:
"""Путь к sysdevcleaner.ico: сначала внутри сборки, потом рядом с проектом."""
names = ("sysdevcleaner.ico",)
roots = []
meipass = getattr(sys, "_MEIPASS", "")
if meipass:
roots.append(meipass)
roots.append(os.path.dirname(os.path.abspath(sys.executable)))
roots.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
roots.append(os.getcwd())
for root in roots:
for name in names:
path = os.path.join(root, name)
if os.path.isfile(path):
return path
return ""
def is_admin() -> bool:
if not IS_WIN:
return hasattr(os, "geteuid") and os.geteuid() == 0
try:
return bool(ctypes.windll.shell32.IsUserAnAdmin())
except Exception:
return False
# ------------------------------------------------------------- заголовок ----
def set_window_title(text: str, find_by: str = BOOT_TITLE) -> bool:
"""Поставить окну настоящий заголовок через Unicode-API.
Возвращает True, если окно нашлось и заголовок применён.
"""
if not IS_WIN:
return False
try:
user32 = ctypes.windll.user32
hwnd = user32.FindWindowW(None, find_by)
if not hwnd:
return False
user32.SetWindowTextW(hwnd, text)
return True
except Exception:
return False
# ----------------------------------------------------- повышение прав -------
def _python_launcher() -> str:
"""Для графического режима предпочитаем pythonw.exe — без окна консоли."""
exe = sys.executable
if not exe:
return ""
folder, name = os.path.split(exe)
if name.lower() == "python.exe":
alt = os.path.join(folder, "pythonw.exe")
if os.path.isfile(alt):
return alt
return exe
def _quote(text: str) -> str:
return f'"{text}"' if " " in text and not text.startswith('"') else text
def relaunch_as_admin(extra_args: list[str] | None = None) -> bool:
"""Перезапустить приложение с правами администратора.
True — повышенная копия запущена, текущий процесс должен завершиться;
False — повышать не нужно или не получилось (пользователь нажал «Нет»),
работаем дальше с обычными правами.
"""
if not IS_WIN or is_admin():
return False
if os.environ.get(NO_ELEVATE_ENV):
return False
args = list(extra_args if extra_args is not None else sys.argv[1:])
if is_frozen():
target = sys.executable
params = " ".join(_quote(a) for a in args)
workdir = os.path.dirname(os.path.abspath(sys.executable))
else:
target = _python_launcher()
if not target:
return False
# каталог, в котором лежит пакет sysdevcleaner — оттуда работает `-m sysdevcleaner`
workdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
params = " ".join(["-m", "sysdevcleaner", *(_quote(a) for a in args)])
try:
# ShellExecuteW с глаголом "runas" показывает запрос UAC
rc = ctypes.windll.shell32.ShellExecuteW(
None, "runas", target, params, workdir, 1)
except Exception:
return False
# всё, что больше 32, — успех; иначе обычно ошибка 5 (пользователь отказался)
return int(rc) > 32
def ensure_admin() -> bool:
"""Вызвать перед запуском интерфейса.
True — нужно завершить текущий процесс: работать продолжит повышенная копия.
"""
if is_admin():
return False
return relaunch_as_admin()