Исходный код EJIO.ctk_extender.window.session
## /ctk_extender/window/session.py
from pathlib import Path
from typing import Any, TypedDict
from loguru import logger
from EJIO.utils import StaticClass, Disposable, SystemProperties, SystemPaths, save, load
__all__ = ['WindowSessionManager']
class WindowGeometryData(TypedDict):
width: int
height: int
x: int
y: int
[документация]
class WindowSessionManager(StaticClass, Disposable):
"""
Статический менеджер сохранения и восстановления геометрии окон.
Защищает интерфейс от 'вылета' окон за пределы физических экранов.
"""
__slots__ = ()
_cached_sessions: dict[str, dict[str, Any]] = {}
_is_loaded: bool = False
@classmethod
def _get_session_file_path(cls) -> Path:
"""Возвращает изолированный кроссплатформенный путь к файлу сессий приложения."""
app_data = SystemPaths.app_data
try:
name = SETTINGS.get('APPLICATION').get('NAME')
except Exception:
name = 'DefaultEJIOApp'
app_folder = app_data / name#ApplicationCore.settings.get('application', {}).get('name', 'DefaultEJIOApp')
app_folder.mkdir(parents=True, exist_ok=True)
return app_folder / "window_sessions.json"
[документация]
@classmethod
def load_sessions(cls) -> None:
"""Лениво считывает файл сохраненных сессий с диска."""
if cls._is_loaded:
return
file_path = cls._get_session_file_path()
if file_path.exists():
try:
data = load(dict, file_path)
if not isinstance(data, dict):
raise TypeError(f"File {file_path} is corrupted")
cls._cached_sessions = data
logger.debug(f"Window geometry sessions loaded successfully from '{file_path}'")
except Exception as e:
logger.error(f"Failed to read windows sessions file: {e}")
cls._cached_sessions = {}
cls._is_loaded = True
[документация]
@classmethod
def save_sessions(cls) -> None:
"""Записывает кэшированные сессии окон на диск."""
file_path = cls._get_session_file_path()
try:
save(cls._cached_sessions, file_path)
except Exception as e:
logger.error(f"Error saving window sessions to disk: {e}")
[документация]
@classmethod
def get_stored_geometry(cls, window_name: str) -> WindowGeometryData | None:
"""
Возвращает сохраненную геометрию окна, если она валидна
и полностью помещается в границы активных мониторов.
"""
cls.load_sessions()
if window_name not in cls._cached_sessions:
return None
geo: WindowGeometryData = cls._cached_sessions[window_name]
# Многомониторная кроссплатформенная валидация координат
if cls._is_position_valid(geo["x"], geo["y"]):
return geo
logger.warning(
f"Saved window position '{window_name}' ({geo['x']},{geo['y']})"
f" is invalid for the current monitor configuration. Reset to center."
)
return None
[документация]
@classmethod
def store_geometry(cls, window_name: str, width: int, height: int, x: int, y: int) -> None:
"""Сохраняет геометрию окна в рантайм-кэш менеджера сессий."""
cls.load_sessions()
cls._cached_sessions[window_name] = {
"width": width,
"height": height,
"x": x,
"y": y
}
@classmethod
def _is_position_valid(cls, x: int, y: int) -> bool:
"""
Низкоуровневая кроссплатформенная проверка:
пересекается ли точка (X, Y) хотя бы с одним активным монитором в ОС.
"""
sys_name = SystemProperties.os
# На Windows вызываем высокоэффективную MonitorFromPoint из user32.dll
if sys_name == "windows":
try:
import ctypes
# MONITOR_DEFAULTTONULL = 0 (возвращает 0, если точка вне всех мониторов)
# Передаем координаты как структуру POINT (2 лонга)
class POINT(ctypes.Structure):
_fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]
h_monitor = ctypes.windll.user32.MonitorFromPoint(POINT(x, y), 0)
return h_monitor != 0
except Exception:
pass
# Для linux и macos:
# Мы запрашиваем у графического сервера операционной системы, существует ли
# физический виртуальный экран под точкой x, y.
try:
from ..app.base import Application
root = Application().context_root
if root is not None:
# Метод winfo_containing запрашивает у X11/Wayland/Cocoa виджет под координатами.
# Если точка находится вне всех подключенных мониторов, метод вернет None или упадет.
# Чтобы проверить пустую область экрана, мы смотрим границы виртуального vroot.
v_width = root.winfo_vrootwidth()
v_height = root.winfo_vrootheight()
# Дополнительная проверка вхождения в геометрию виртуального рабочего стола POSIX
if x >= 0 and y >= 0 and x < v_width and y < v_height:
return True
except Exception:
pass
# Резервный кроссплатформенный вариант для Linux/Mac:
# Проверяем, входят ли координаты в виртуальное пространство экранов X11/Cocoa
return x >= 0 and y >= 0
[документация]
@classmethod
def clear(cls) -> None:
"""Реализация Disposable: сбрасывает кэш сессий."""
cls._cached_sessions.clear()
cls._is_loaded = False