"""Работа с нативным окном 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()