Исходный код EJIO.utils.system.properties

## /utils/system/properties.py

from pathlib import Path
from zoneinfo import ZoneInfo
from ..interface import ReadOnlyMeta

__all__ = [
    'SystemProperties',
]


[документация] class SystemProperties(metaclass=ReadOnlyMeta): """ Неизменяемая статическая структура, предоставляющая базовые свойства системы. Не поддерживает создание экземпляров и модификацию данных. """ __slots__ = () @staticmethod def os() -> str: """Возвращает имя операционной системы (например: 'windows', 'linux', 'darwin').""" import platform sys_type = platform.system().lower() if sys_type == "windows": return "windows" if sys_type == "darwin": return "darwin" return "linux" @staticmethod def os_release() -> str: """Возвращает версию релиза ОС (например: '10', '11', '5.15.0-71-generic').""" import platform return platform.release() @staticmethod def theme() -> str: """ Кроссплатформенно определяет текущую системную тему оформления (dark/light). """ sys_name = SystemProperties.os # Сценарий 1: Windows (считываем значение из системного реестра) if sys_name == "windows": try: import winreg registry_key = winreg.OpenKey( winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" ) value, _ = winreg.QueryValueEx(registry_key, "AppsUseLightTheme") winreg.CloseKey(registry_key) return "light" if value == 1 else "dark" except Exception: pass # Сценарий 2: macOS (Darwin) elif sys_name == "darwin": try: import subprocess result = subprocess.run( ["defaults", "read", "-g", "AppleInterfaceStyle"], capture_output=True, text=True ) return "dark" if "Dark" in result.stdout else "light" except Exception: pass import os # Сценарий 3: Резервный вариант для Linux и контейнеров mode = os.environ.get("GLOBAL_THEME") or os.environ.get("COLORTERM") or "light" return "dark" if "dark" in mode.lower() else "light" @staticmethod def language() -> str: """ Возвращает точный кроссплатформенный язык интерфейса операционной системы (например: 'ru_RU', 'en_US', 'de_DE'). """ sys_name = SystemProperties.os # Сценарий 1: Windows (считываем язык интерфейса ОС из системного реестра) if sys_name == "windows": try: import ctypes # Создаем буфер для хранения списка языков интерфейса пользователя (MUI) num_languages = ctypes.c_ulong(0) buffer_size = ctypes.c_ulong(0) # Константа MUI_LANGUAGE_NAME (0x8) заставляет Windows вернуть имена вроде "ru-RU" # Сначала вызываем функцию с пустым буфером, чтобы узнать требуемый размер памяти ctypes.windll.kernel32.GetUserDefaultUILanguage() # Резервный ID на всякий случай # Самый точный метод: GetUserPreferredUILanguages # Он возвращает список языков интерфейса в порядке их приоритета у пользователя if ctypes.windll.kernel32.GetUserPreferredUILanguages(8, ctypes.byref(num_languages), None, ctypes.byref(buffer_size)): buffer = ctypes.create_unicode_buffer(buffer_size.value) if ctypes.windll.kernel32.GetUserPreferredUILanguages(8, ctypes.byref(num_languages), buffer, ctypes.byref(buffer_size)): # Строка возвращается в виде списка, разделенного нулевыми байтами (null-terminated). # Берем самый первый (приоритетный) язык интерфейса в системе raw_lang = buffer.value.split('\x00')[0] if raw_lang: if "-" in raw_lang: return raw_lang.replace("-", "_") return f"{raw_lang}_{raw_lang.upper()}" # Резервный быстрый вариант для старых сборок Windows через LANGID (например, 1049 для RU) lang_id = ctypes.windll.kernel32.GetUserDefaultUILanguage() # Переводим LANGID в строковый код локали через макрос locale_str = ctypes.create_unicode_buffer(85) if ctypes.windll.kernel32.LCIDToLocaleName(lang_id, locale_str, 85, 0): value = locale_str.value if "-" in value: return value.replace("-", "_") return value except Exception: pass # Сценарий 2: macOS / Darwin (считываем массив предпочитаемых языков пользователя) elif sys_name == "darwin": try: import subprocess result = subprocess.run( ["defaults", "read", "-g", "AppleLanguages"], capture_output=True, text=True ) # Вывод выглядит как: (\n "ru-RU",\n "en-US"\n) # Извлекаем первый (самый приоритетный) язык интерфейса for line in result.stdout.splitlines(): if '"' in line: lang = line.split('"')[1] if "-" in lang: return lang.replace("-", "_") return f"{lang}_{lang.upper()}" except Exception: pass import os # Сценарий 3: Linux и резервный вариант для всех ОС # Перебираем стандартные переменные окружения, куда Linux/Unix пишет локаль for env_var in ("LANG", "LC_ALL", "LC_CTYPE", "LANGUAGE"): local_val = os.environ.get(env_var) if local_val: clean_val = str(local_val.split(".")[0]) if "-" in clean_val: return clean_val.replace("-", "_") return clean_val return "en_US" @staticmethod def timezone_name() -> str: """Возвращает имя текущего часового пояса системы (например: 'UTC', 'MSK').""" import time return time.tzname[time.daylight] @staticmethod def timezone() -> ZoneInfo: """ Возвращает объект ZoneInfo текущего часового пояса системы для удобной конвертации времени через datetime. """ import time # time.tzname[0] может вернуть локальное имя, пытаемся получить IANA-идентификатор try: import sys # На Unix-системах это самый надежный способ узнать IANA-имя (например, 'Europe/Moscow') if sys.platform != "win32": import tzlocal return ZoneInfo(tzlocal.get_localzone_name()) except Exception: pass # Резервный вариант по смещению времени, если IANA имя не определилось # (ZoneInfo умеет работать со смещениями через объекты или строки конфигурации) offset_seconds = -time.timezone if not time.daylight else -time.altzone return ZoneInfo(f"Etc/GMT{-offset_seconds // 3600:+d}".replace("+-", "-").replace("++", "+")) @staticmethod def date_format() -> str: """Возвращает стандартный формат даты, принятый в текущей локали.""" import sys import locale # Для России это обычно '%d.%m.%Y', для США '%m/%d/%Y' # Извлекаем базовый строковый маркер даты из настроек локали if sys.platform != "win32": try: return locale.nl_langinfo(locale.D_FMT) # type: ignore[attr-defined] # Работает на Unix-системах except (AttributeError, ValueError): pass return "%d.%m.%Y" if "ru" in SystemProperties.language.lower() else "%m/%d/%Y" # type: ignore @staticmethod def screen_width() -> int: """Возвращает точную физическую ширину главного (первичного) монитора в системе.""" sys_name = SystemProperties.os # --- 1. WINDOWS USER32 API --- if sys_name == "windows": try: import ctypes # 0 = SM_CXSCREEN (Ширина первичного монитора) return int(ctypes.windll.user32.GetSystemMetrics(0)) except Exception: return 1920 # --- 2. MACOS (DARWIN) CORE GRAPHICS API --- elif sys_name == "darwin": try: import ctypes import ctypes.util # Загружаем ApplicationServices, содержащий CoreGraphics API lib_path = ctypes.util.find_library('ApplicationServices') if lib_path: core_graphics = ctypes.CDLL(lib_path) # CGMainDisplayID() возвращает ID главного дисплея (обычно 1) main_display = core_graphics.CGMainDisplayID() # CGDisplayPixelsWide возвращает точную физическую ширину экрана width = core_graphics.CGDisplayPixelsWide(main_display) if width > 0: return int(width) except Exception: pass # Резервный консольный парсер для Mac через системный профайлер Displays try: import subprocess import re cmd = "system_profiler SPDisplaysDataType | grep Resolution" output = subprocess.check_output(cmd, shell=True, text=True, stderr=subprocess.DEVNULL) match = re.search(r"(\d+)\s*x\s*(\d+)", output) if match: return int(match.group(1)) except Exception: pass return 1440 # Безопасный кроссплатформенный Retina-откат # --- 3. LINUX (X11 / WAYLAND XRANDR ENGINE) --- else: try: # Сканируем утилиту xrandr на наличие флага 'primary' import subprocess import re output = subprocess.check_output(["xrandr", "--current"], text=True, stderr=subprocess.DEVNULL) for line in output.splitlines(): if "primary" in line: match = re.search(r"(\d+)x(\d+)\+", line) if match: return int(match.group(1)) except Exception: try: # Резервный сборщик для старых версий графических серверов X11 import subprocess import re output = subprocess.check_output(["xdpyinfo"], text=True, stderr=subprocess.DEVNULL) for line in output.splitlines(): if "dimensions:" in line: match = re.search(r"(\d+)x(\d+)\s+pixels", line) if match: return int(match.group(1)) except Exception: pass return 1920 @staticmethod def screen_height() -> int: """Возвращает точную физическую высоту главного (первичного) монитора в системе.""" sys_name = SystemProperties.os # --- 1. WINDOWS USER32 API --- if sys_name == "windows": try: import ctypes # 1 = SM_CYSCREEN (Высота первичного монитора) return int(ctypes.windll.user32.GetSystemMetrics(1)) except Exception: return 1080 # --- 2. MACOS (DARWIN) CORE GRAPHICS API --- elif sys_name == "darwin": try: import ctypes import ctypes.util lib_path = ctypes.util.find_library('ApplicationServices') if lib_path: core_graphics = ctypes.CDLL(lib_path) main_display = core_graphics.CGMainDisplayID() height = core_graphics.CGDisplayPixelsHigh(main_display) if height > 0: return int(height) except Exception: pass try: import subprocess import re cmd = "system_profiler SPDisplaysDataType | grep Resolution" output = subprocess.check_output(cmd, shell=True, text=True, stderr=subprocess.DEVNULL) match = re.search(r"(\d+)\s*x\s*(\d+)", output) if match: return int(match.group(2)) except Exception: pass return 900 # --- 3. LINUX (X11 / WAYLAND XRANDR ENGINE) --- else: try: import subprocess import re output = subprocess.check_output(["xrandr", "--current"], text=True, stderr=subprocess.DEVNULL) for line in output.splitlines(): if "primary" in line: match = re.search(r"(\d+)x(\d+)\+", line) if match: return int(match.group(2)) except Exception: try: import subprocess import re output = subprocess.check_output(["xdpyinfo"], text=True, stderr=subprocess.DEVNULL) for line in output.splitlines(): if "dimensions:" in line: match = re.search(r"(\d+)x(\d+)\s+pixels", line) if match: return int(match.group(2)) except Exception: pass return 1080 @staticmethod def is_64bit() -> bool: """Определяет, является ли архитектура процессора 64-битной.""" import sys return sys.maxsize > 2**32 @staticmethod def python_version() -> str: """Возвращает текущую версию интерпретатора Python.""" import sys return f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" @staticmethod def cpu_count() -> int: """Возвращает количество логических ядер процессора в системе. Если не определено, вернет 1.""" import os return os.cpu_count() or 1 @staticmethod def user_home() -> Path: """Возвращает объект Path, указывающий на домашнюю директорию текущего пользователя.""" return Path.home() @staticmethod def user_name() -> str: """Возвращает имя текущего пользователя в операционной системе.""" import sys import os # Кроссплатформенный способ получения имени пользователя return os.getlogin() if sys.platform == "win32" else os.environ.get("USER", os.environ.get("LOGNAME", "unknown"))
[документация] @classmethod def clear(cls) -> None: """Реализация контракта Disposable..""" pass
[документация] @classmethod def destroy(cls) -> None: """Реализация контракта Disposable.""" pass