mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-09-06 03:20:49 +02:00
118 lines
3.7 KiB
Python
118 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
import tkinter as tk
|
|
from dataclasses import dataclass
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class MonitorBounds:
|
|
x: int
|
|
y: int
|
|
width: int
|
|
height: int
|
|
primary: bool = False
|
|
|
|
@property
|
|
def center(self) -> tuple[int, int]:
|
|
return self.x + self.width // 2, self.y + self.height // 2
|
|
|
|
def as_tuple(self) -> tuple[int, int, int, int]:
|
|
return self.x, self.y, self.width, self.height
|
|
|
|
|
|
def available_monitors(root: tk.Misc | None = None) -> list[MonitorBounds]:
|
|
try:
|
|
from screeninfo import get_monitors
|
|
|
|
monitors = [
|
|
MonitorBounds(
|
|
int(monitor.x),
|
|
int(monitor.y),
|
|
int(monitor.width),
|
|
int(monitor.height),
|
|
bool(getattr(monitor, "is_primary", False)),
|
|
)
|
|
for monitor in get_monitors()
|
|
]
|
|
if monitors:
|
|
return monitors
|
|
except Exception:
|
|
pass
|
|
if root is None:
|
|
return []
|
|
return [
|
|
MonitorBounds(
|
|
int(root.winfo_vrootx()),
|
|
int(root.winfo_vrooty()),
|
|
int(root.winfo_vrootwidth()),
|
|
int(root.winfo_vrootheight()),
|
|
True,
|
|
)
|
|
]
|
|
|
|
|
|
def preferred_monitor(
|
|
root: tk.Misc,
|
|
saved_bounds: tuple[int, int, int, int] | None = None,
|
|
) -> MonitorBounds:
|
|
monitors = available_monitors(root)
|
|
if saved_bounds:
|
|
for monitor in monitors:
|
|
if monitor.as_tuple() == saved_bounds:
|
|
return monitor
|
|
return next((monitor for monitor in monitors if monitor.primary), monitors[0])
|
|
|
|
|
|
def monitor_for_geometry(root: tk.Misc, geometry: str) -> MonitorBounds:
|
|
monitors = available_monitors(root)
|
|
parsed = parse_geometry(geometry)
|
|
if not parsed:
|
|
return next((monitor for monitor in monitors if monitor.primary), monitors[0])
|
|
width, height, x, y = parsed
|
|
center_x, center_y = x + width // 2, y + height // 2
|
|
containing = [
|
|
monitor
|
|
for monitor in monitors
|
|
if monitor.x <= center_x < monitor.x + monitor.width
|
|
and monitor.y <= center_y < monitor.y + monitor.height
|
|
]
|
|
if containing:
|
|
return containing[0]
|
|
return min(monitors, key=lambda monitor: _distance_squared(monitor.center, (center_x, center_y)))
|
|
|
|
|
|
def centered_geometry(width: int, height: int, monitor: MonitorBounds) -> str:
|
|
fitted_width = min(width, max(640, monitor.width - 80))
|
|
fitted_height = min(height, max(480, monitor.height - 80))
|
|
x = monitor.x + (monitor.width - fitted_width) // 2
|
|
y = monitor.y + (monitor.height - fitted_height) // 2
|
|
return format_geometry(fitted_width, fitted_height, x, y)
|
|
|
|
|
|
def ensure_visible_geometry(geometry: str, monitor: MonitorBounds) -> str:
|
|
parsed = parse_geometry(geometry)
|
|
if not parsed:
|
|
return centered_geometry(1500, 860, monitor)
|
|
width, height, x, y = parsed
|
|
width = min(width, max(640, monitor.width - 40))
|
|
height = min(height, max(480, monitor.height - 40))
|
|
x = min(max(x, monitor.x), monitor.x + monitor.width - width)
|
|
y = min(max(y, monitor.y), monitor.y + monitor.height - height)
|
|
return format_geometry(width, height, x, y)
|
|
|
|
|
|
def parse_geometry(geometry: str) -> tuple[int, int, int, int] | None:
|
|
match = re.fullmatch(r"(\d+)x(\d+)([+-]\d+)([+-]\d+)", geometry.strip())
|
|
if not match:
|
|
return None
|
|
return tuple(int(value) for value in match.groups()) # type: ignore[return-value]
|
|
|
|
|
|
def format_geometry(width: int, height: int, x: int, y: int) -> str:
|
|
return f"{width}x{height}{x:+d}{y:+d}"
|
|
|
|
|
|
def _distance_squared(left: tuple[int, int], right: tuple[int, int]) -> int:
|
|
return (left[0] - right[0]) ** 2 + (left[1] - right[1]) ** 2
|