Files
HeagBoKaT b01cf37e66 Первый выпуск 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>
2026-09-11 14:55:00 +03:00

145 lines
5.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Работа с нативным окном 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()