Repository navigation
Expand file tree
/
Copy pathpet_config.py
More file actions
97 lines (82 loc) · 3.65 KB
/
Copy pathpet_config.py
File metadata and controls
97 lines (82 loc) · 3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
"""pet_config · 桌宠本地配置(~/.comi-pet/config.json,Tk 与 Swift 端共用同一文件)。
字段(version 1):
notifications "auto" | "always" | "off" 系统通知(auto=桌宠隐藏/内嵌时才弹)
update_check "auto" | "off" 启动时检查 GitHub 新版本(默认 auto)
welcomed bool 首次启动的玩法提示是否已展示过
remember_position bool 重启后回到上次位置
position {"x": int, "y": int, "cs": "topleft"|"bottomleft"}
cs 标记坐标系:Tk 为屏幕左上原点,Swift/Cocoa 为左下原点;
只恢复与自身坐标系一致的记录,跨平台互不误用。
原子写、损坏回落默认、绝不抛异常(返回 bool/默认值),与 language_preferences
同级的最小本地偏好;不含凭据,不含代理内容。
"""
import json
import os
from pathlib import Path
import comi_home
VERSION = 1
NOTIFICATION_MODES = ("auto", "always", "off")
UPDATE_CHECK_MODES = ("auto", "off")
COORD_SYSTEMS = ("topleft", "bottomleft")
DEFAULTS = {
"version": VERSION,
"notifications": "auto",
"update_check": "auto",
"welcomed": False,
"remember_position": True,
"position": None,
}
def config_path():
return Path(comi_home.env("COMI_CONFIG_DIR") or comi_home.data_dir()) / "config.json"
def _normalize(raw):
cfg = dict(DEFAULTS)
if not isinstance(raw, dict):
return cfg
if raw.get("notifications") in NOTIFICATION_MODES:
cfg["notifications"] = raw["notifications"]
if raw.get("update_check") in UPDATE_CHECK_MODES:
cfg["update_check"] = raw["update_check"]
if isinstance(raw.get("welcomed"), bool):
cfg["welcomed"] = raw["welcomed"]
if isinstance(raw.get("remember_position"), bool):
cfg["remember_position"] = raw["remember_position"]
pos = raw.get("position")
if (isinstance(pos, dict) and isinstance(pos.get("x"), int)
and isinstance(pos.get("y"), int) and pos.get("cs") in COORD_SYSTEMS):
cfg["position"] = {"x": pos["x"], "y": pos["y"], "cs": pos["cs"]}
return cfg
def load(path=None):
path = Path(path) if path is not None else config_path()
try:
raw = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, ValueError):
raw = None
return _normalize(raw)
def save(cfg, path=None):
"""原子写回;失败不抛异常,返回 False。只写入已知字段。"""
path = Path(path) if path is not None else config_path()
clean = _normalize(cfg)
try:
path.parent.mkdir(parents=True, exist_ok=True)
tmp = path.with_name(f"{path.name}.tmp-{os.getpid()}")
tmp.write_text(json.dumps(clean, ensure_ascii=False, indent=2),
encoding="utf-8")
os.replace(tmp, path)
except OSError:
return False
return True
def valid_position(pos, screen_w, screen_h, size, cs, margin=8):
"""把记忆位置夹回屏幕内;非法/越界过多返回 None。
完全离屏(例如换了显示器布局)也返回 None,让调用侧走默认出生点。
"""
if not (isinstance(pos, dict) and pos.get("cs") == cs):
return None
try:
x, y = int(pos["x"]), int(pos["y"])
except (KeyError, TypeError, ValueError):
return None
if x < -size or y < -size or x > screen_w or y > screen_h:
return None # 位置完全不在任何合理屏幕范围内
x = max(-size // 2, min(x, screen_w - size // 2))
y = max(margin, min(y, screen_h - size // 2))
return {"x": x, "y": y, "cs": cs}