Coverage for graphica/gui/file_association.py: 82.3%
67 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
1"""Windows で .gra と .graphica を Graphica に結び付ける(利用者の範囲 HKCU\\Software\\Classes だけを触る)。
3インストーラーを使わず exe だけを置いた人のため、環境設定のボタンから登録・解除する。インストーラーは同じ名前で自分で登録する。
4ソースや pip から動かしているときは、ダブルクリックで起動する exe が無いので登録しない。
5"""
6import os
7import sys
9PROG_ID = "Graphica.Project"
10PROG_ID_DESCRIPTION = "Graphica プロジェクト"
11ASSOCIATED_EXTENSIONS = (".gra", ".graphica")
12_CLASSES_ROOT = r"Software\Classes"
15def _winreg():
16 import winreg
17 return winreg
20def is_supported():
21 """登録できるのは、Windows の exe(PyInstaller で固めたもの)から動かしているときだけ。"""
22 return sys.platform == "win32" and bool(getattr(sys, "frozen", False))
25def open_command(executable=None):
26 return f'"{executable or sys.executable}" "%1"'
29def _set_default(winreg, path, value):
30 with winreg.CreateKeyEx(winreg.HKEY_CURRENT_USER, path, 0, winreg.KEY_WRITE) as key:
31 winreg.SetValueEx(key, "", 0, winreg.REG_SZ, value)
34def _read_default(winreg, path):
35 try:
36 with winreg.OpenKey(winreg.HKEY_CURRENT_USER, path) as key:
37 return winreg.QueryValueEx(key, "")[0]
38 except OSError:
39 return None
42def _delete_tree(winreg, path):
43 try:
44 with winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, winreg.KEY_ALL_ACCESS) as key:
45 while True:
46 try:
47 child = winreg.EnumKey(key, 0)
48 except OSError:
49 break
50 _delete_tree(winreg, path + "\\" + child)
51 winreg.DeleteKey(winreg.HKEY_CURRENT_USER, path)
52 except FileNotFoundError:
53 pass
56def _notify_shell():
57 """エクスプローラーにアイコンと関連付けを読み直させる。失敗しても登録自体は済んでいる。"""
58 try:
59 import ctypes
60 shcne_assocchanged, shcnf_idlist = 0x08000000, 0x0000
61 ctypes.windll.shell32.SHChangeNotify(shcne_assocchanged, shcnf_idlist, None, None)
62 except (AttributeError, OSError):
63 pass
66def register(executable=None, winreg=None, notify_shell=True):
67 winreg = winreg or _winreg()
68 executable = executable or sys.executable
69 prog_id_path = _CLASSES_ROOT + "\\" + PROG_ID
70 _set_default(winreg, prog_id_path, PROG_ID_DESCRIPTION)
71 _set_default(winreg, prog_id_path + r"\DefaultIcon", f'"{executable}",0')
72 _set_default(winreg, prog_id_path + r"\shell\open\command", open_command(executable))
73 for ext in ASSOCIATED_EXTENSIONS:
74 _set_default(winreg, _CLASSES_ROOT + "\\" + ext, PROG_ID)
75 if notify_shell: 75 ↛ 76line 75 didn't jump to line 76 because the condition on line 75 was never true
76 _notify_shell()
79def unregister(winreg=None, notify_shell=True):
80 """自分が結び付けた拡張子だけを外す(ほかのソフトに変えられていれば触らない)。"""
81 winreg = winreg or _winreg()
82 for ext in ASSOCIATED_EXTENSIONS:
83 path = _CLASSES_ROOT + "\\" + ext
84 if _read_default(winreg, path) == PROG_ID:
85 _delete_tree(winreg, path)
86 _delete_tree(winreg, _CLASSES_ROOT + "\\" + PROG_ID)
87 if notify_shell: 87 ↛ 88line 87 didn't jump to line 88 because the condition on line 87 was never true
88 _notify_shell()
91def is_registered(executable=None, winreg=None):
92 """このアプリの exe で開くよう登録されているか。"""
93 winreg = winreg or _winreg()
94 command = _read_default(winreg, _CLASSES_ROOT + "\\" + PROG_ID + r"\shell\open\command")
95 if command is None or os.path.normcase(command) != os.path.normcase(open_command(executable)):
96 return False
97 return all(_read_default(winreg, _CLASSES_ROOT + "\\" + ext) == PROG_ID for ext in ASSOCIATED_EXTENSIONS)