Первый выпуск 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

111
make_icon.py Normal file
View File

@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""Генерация значка SysDevCleaner.
Значок рисуется геометрией, а не глифом шрифта: тонкая «палочка» из Nerd Font
на 16×16 превращалась в неразборчивое пятно. Здесь метла нарисована жирными
примитивами и остаётся узнаваемой даже в самом мелком размере.
python make_icon.py
Результат — ``sysdevcleaner.ico`` (размеры 16…256) и ``sysdevcleaner.png``
256×256 для README.
"""
from __future__ import annotations
import os
from PIL import Image, ImageDraw
ROOT = os.path.dirname(os.path.abspath(__file__))
ICO = os.path.join(ROOT, "sysdevcleaner.ico")
PNG = os.path.join(ROOT, "sysdevcleaner.png")
SIZES = (16, 24, 32, 48, 64, 128, 256)
R = 1024 # рисуем крупно, потом уменьшаем — края мягче
BG_TOP = (82, 154, 226) # accent из тёмной темы, чуть светлее
BG_BOTTOM = (42, 88, 150)
FG = 255 # метла белая, задаётся через маску
def gradient(size: int) -> Image.Image:
img = Image.new("RGB", (1, size))
d = ImageDraw.Draw(img)
for y in range(size):
t = y / max(1, size - 1)
d.point((0, y), tuple(int(a + (b - a) * t)
for a, b in zip(BG_TOP, BG_BOTTOM)))
return img.resize((size, size), Image.BICUBIC)
def rounded_mask(size: int, radius_ratio: float = 0.22) -> Image.Image:
mask = Image.new("L", (size, size), 0)
ImageDraw.Draw(mask).rounded_rectangle(
[0, 0, size - 1, size - 1], radius=int(size * radius_ratio), fill=255)
return mask
def broom_mask() -> Image.Image:
"""Маска метлы: рисуем вертикально, потом поворачиваем.
Вертикальная заготовка проще в координатах, а наклон даёт узнаваемый силуэт.
"""
pad = R // 2
m = Image.new("L", (R + pad, R + pad), 0)
d = ImageDraw.Draw(m)
cx = (R + pad) // 2
# черенок
d.rounded_rectangle([cx - 52, 150, cx + 52, 640], radius=52, fill=FG)
# обхват под черенком
d.rounded_rectangle([cx - 132, 610, cx + 132, 710], radius=40, fill=FG)
# ворс — расширяющаяся трапеция
d.polygon([(cx - 118, 700), (cx + 118, 700),
(cx + 250, 1030), (cx - 250, 1030)], fill=FG)
# прорези в ворсе: режем маску, а не рисуем поверх фона
for dx in (-125, 0, 125):
d.line([(cx + dx * 0.62, 790), (cx + dx, 1040)], fill=0, width=34)
# нижний край чуть скруглим, срезав уголки
d.polygon([(cx - 250, 1030), (cx - 250, 1100), (cx + 250, 1100),
(cx + 250, 1030)], fill=0)
m = m.rotate(-38, resample=Image.BICUBIC, expand=True,
center=(cx, (R + pad) // 2))
# вписываем в плитку: обрезаем по содержимому и масштабируем до 66%
bbox = m.getbbox()
m = m.crop(bbox)
target = int(R * 0.66)
scale = target / max(m.size)
m = m.resize((max(1, int(m.width * scale)), max(1, int(m.height * scale))),
Image.LANCZOS)
out = Image.new("L", (R, R), 0)
out.paste(m, ((R - m.width) // 2, (R - m.height) // 2))
return out
def build() -> Image.Image:
base = gradient(R).convert("RGBA")
base.putalpha(rounded_mask(R))
mask = broom_mask()
white = Image.new("RGBA", (R, R), (255, 255, 255, 0))
white.putalpha(mask)
return Image.alpha_composite(base, white)
def main() -> int:
icon = build()
icon.resize((256, 256), Image.LANCZOS).save(PNG)
print(f" {PNG}")
frames = [icon.resize((s, s), Image.LANCZOS) for s in SIZES]
frames[-1].save(ICO, format="ICO",
sizes=[(s, s) for s in SIZES], append_images=frames[:-1])
print(f" {ICO} (размеры: {', '.join(str(s) for s in SIZES)})")
return 0
if __name__ == "__main__":
raise SystemExit(main())