-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp_paths.py
More file actions
131 lines (107 loc) · 6.37 KB
/
Copy pathapp_paths.py
File metadata and controls
131 lines (107 loc) · 6.37 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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""Пути приложения и автозапуск через реестр.
Отдельно от app.py, потому что этим пользуется и окно настроек: тянуть
ради двух функций весь app.py (а с ним модель и хоткеи) - лишнее.
"""
import os
import sys
import platform_api
# winreg - только Windows: и сам модуль (ModuleNotFoundError на маке), и всё, что
# им читается/пишется (автозапуск через HKCU\...\Run). На macOS автозапуск - это
# LaunchAgent, отдельная задача; пока функции ниже отвечают заглушками.
if platform_api.IS_WINDOWS:
import winreg
def _app_dir() -> str:
"""Папка, рядом с которой живут данные: конфиг, лог, история, модель.
При обычном запуске это папка с исходниками. В собранном .exe __file__
указывает внутрь временной распаковки PyInstaller - она стирается при
выходе, и настройки с историей пропадали бы после каждого закрытия.
Поэтому там берём папку самого .exe.
"""
if getattr(sys, "frozen", False):
return os.path.dirname(os.path.abspath(sys.executable))
return os.path.dirname(os.path.abspath(__file__))
APP_DIR = _app_dir()
CONFIG_PATH = os.path.join(APP_DIR, "config.json")
# Личный конфиг в репозиторий не уезжает (словарь и сниппеты - личное), поэтому
# у свежего клона его нет: приложение делает свой из примера. См. config.bootstrap.
#
# Пример ищем не рядом с конфигом, а рядом с кодом: в собранном .exe он лежит
# ВНУТРИ, во временной распаковке (sys._MEIPASS), а рядом с .exe его нет -
# оттуда и копируем наружу при первом запуске.
EXAMPLE_PATH = os.path.join(
getattr(sys, "_MEIPASS", None) or os.path.dirname(os.path.abspath(__file__)),
"config.example.json")
LOG_PATH = os.path.join(APP_DIR, "flow.log")
HISTORY_PATH = os.path.join(APP_DIR, "history.jsonl")
MODELS_DIR = os.path.join(APP_DIR, "models")
RUN_KEY = r"Software\Microsoft\Windows\CurrentVersion\Run"
RUN_NAME = "FlowLocal"
def autostart_command() -> str:
"""Команда автозапуска для реестра.
Собранный .exe запускает сам себя - никакого Python рядом нет.
Из исходников зовём pythonw, а не python: иначе при каждом входе в систему
всплывает и висит чёрное окно консоли.
`--silent` отличает вход в систему от клика по ярлыку, и это не мелочь:
команда была одна и та же, поэтому «показывать окно при запуске» включить
было нельзя - оно всплывало бы при каждой загрузке Windows. Теперь ярлык
открывает настройки, а автозапуск молча садится в трей.
"""
if getattr(sys, "frozen", False):
return f'"{sys.executable}" --silent'
pythonw = os.path.join(os.path.dirname(sys.executable), "pythonw.exe")
if not os.path.exists(pythonw):
pythonw = sys.executable
return f'"{pythonw}" "{os.path.join(APP_DIR, "app.py")}" --silent'
def autostart_enabled() -> bool:
if not platform_api.IS_WINDOWS:
return False # автозапуск на macOS пока не заведён - см. шапку
try:
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, RUN_KEY) as k:
value, _ = winreg.QueryValueEx(k, RUN_NAME)
return bool(value)
except FileNotFoundError:
return False
except OSError:
return False
def set_autostart(enabled: bool) -> None:
if not platform_api.IS_WINDOWS:
platform_api.note_unsupported("автозапуск")
return
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, RUN_KEY, 0, winreg.KEY_SET_VALUE) as k:
if enabled:
winreg.SetValueEx(k, RUN_NAME, 0, winreg.REG_SZ, autostart_command())
else:
try:
winreg.DeleteValue(k, RUN_NAME)
except FileNotFoundError:
pass
def autostart_command_current() -> str:
"""Что сейчас записано в реестре. Пусто - записи нет.
Нужна тому, кто решает, лечить запись или не трогать: из исходников
переписывать команду, указывающую на установленную сборку, нельзя.
"""
if not platform_api.IS_WINDOWS:
return ""
try:
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, RUN_KEY) as k:
value, _ = winreg.QueryValueEx(k, RUN_NAME)
return str(value or "")
except OSError:
return ""
def autostart_command_stale() -> bool:
"""Записанная в реестре команда разошлась с той, что нужна сегодня.
Такое бывает не только от нашей правки: папку приложения перенесли, Python
обновили, появился новый флаг. Пока команда не совпадает, автозапуск либо
не сработает, либо сработает не так - например, откроет окно настроек при
каждом входе в систему, потому что в старой записи нет `--silent`.
Чинится на старте (app.py). Не при выключенном автозапуске: чего нет, то и
чинить нечего.
"""
if not platform_api.IS_WINDOWS:
return False
try:
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, RUN_KEY) as k:
value, _ = winreg.QueryValueEx(k, RUN_NAME)
except OSError:
return False
return bool(value) and str(value) != autostart_command()