Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit eeb0840

Browse filesBrowse files
committed
flash- 2.5 beta complicated things, chatgpt + manual restored order
1 parent 15f6202 commit eeb0840
Copy full SHA for eeb0840

File tree

Expand file treeCollapse file tree

1 file changed

+93
-222
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+93
-222
lines changed

‎winpython/associate.py

Copy file name to clipboardExpand all lines: winpython/associate.py
+93-222Lines changed: 93 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,21 @@
88
import sys
99
import os
1010
from pathlib import Path
11-
import platform
1211
import importlib.util
1312
import winreg
1413
from winpython import utils
1514
from argparse import ArgumentParser
1615

17-
# --- Constants ---
18-
KEY_C = r"Software\Classes\%s"
19-
KEY_CP = r"Software\Classes"
20-
KEY_S = r"Software\Python"
21-
KEY_S0 = KEY_S + r"\WinPython" # was PythonCore before PEP-0514
22-
EWI = "Edit with IDLE"
23-
EWS = "Edit with Spyder"
24-
DROP_HANDLER_CLSID = "{60254CA5-953B-11CF-8C96-00AA00B8708C}"
2516

2617
# --- Helper functions for Registry ---
2718

2819
def _set_reg_value(root, key_path, name, value, reg_type=winreg.REG_SZ, verbose=False):
2920
"""Helper to create key and set a registry value using CreateKeyEx."""
3021
rootkey_name = "HKEY_CURRENT_USER" if root == winreg.HKEY_CURRENT_USER else "HKEY_LOCAL_MACHINE"
22+
if verbose:
23+
print(f"{rootkey_name}\\{key_path}\\{name if name else ''}:{value}")
3124
try:
3225
# Use CreateKeyEx with context manager for automatic closing
33-
# KEY_WRITE access is needed to set values
34-
35-
if verbose:
36-
print(f"{rootkey_name}\\{key_path}\\{name if name else ''}:{value}")
3726
with winreg.CreateKeyEx(root, key_path, 0, winreg.KEY_WRITE) as key:
3827
winreg.SetValueEx(key, name, 0, reg_type, value)
3928
except OSError as e:
@@ -42,9 +31,9 @@ def _set_reg_value(root, key_path, name, value, reg_type=winreg.REG_SZ, verbose=
4231
def _delete_reg_key(root, key_path, verbose=False):
4332
"""Helper to delete a registry key, ignoring if not found."""
4433
rootkey_name = "HKEY_CURRENT_USER" if root == winreg.HKEY_CURRENT_USER else "HKEY_LOCAL_MACHINE"
34+
if verbose:
35+
print(f"{rootkey_name}\\{key_path}")
4536
try:
46-
if verbose:
47-
print(f"{rootkey_name}\\{key_path}")
4837
# DeleteKey can only delete keys with no subkeys.
4938
# For keys with (still) subkeys, use DeleteKeyEx on the parent key if available
5039
winreg.DeleteKey(root, key_path)
@@ -79,7 +68,6 @@ def _get_shortcut_data(target, current=True, has_pywin32=False):
7968
bname, ext = Path(name).stem, Path(name).suffix
8069
if ext.lower() == ".exe":
8170
# Path for the shortcut file in the start menu folder
82-
# This depends on utils.create_winpython_start_menu_folder creating the right path
8371
shortcut_name = str(Path(utils.create_winpython_start_menu_folder(current=current)) / bname) + '.lnk'
8472
data.append(
8573
(
@@ -90,128 +78,86 @@ def _get_shortcut_data(target, current=True, has_pywin32=False):
9078
)
9179
return data
9280

93-
# --- Registry Entry Definitions ---
94-
95-
# Structure: (key_path, value_name, value, reg_type)
96-
# Use None for value_name to set the default value of the key
97-
REGISTRY_ENTRIES = []
98-
99-
# --- Extensions ---
100-
EXTENSIONS = {
101-
".py": "Python.File",
102-
".pyw": "Python.NoConFile",
103-
".pyc": "Python.CompiledFile",
104-
".pyo": "Python.CompiledFile",
105-
}
106-
for ext, file_type in EXTENSIONS.items():
107-
REGISTRY_ENTRIES.append((KEY_C % ext, None, file_type))
108-
109-
# --- MIME types ---
110-
MIME_TYPES = {
111-
".py": "text/plain",
112-
".pyw": "text/plain",
113-
}
114-
for ext, mime_type in MIME_TYPES.items():
115-
REGISTRY_ENTRIES.append((KEY_C % ext, "Content Type", mime_type))
116-
117-
# --- Verbs (Open, Edit with IDLE, Edit with Spyder) ---
118-
# These depend on the python/pythonw/spyder paths
119-
def _get_verb_entries(target):
120-
python = str((Path(target) / "python.exe").resolve())
121-
pythonw = str((Path(target) / "pythonw.exe").resolve())
122-
spyder_exe = str((Path(target).parent / "Spyder.exe").resolve())
123-
124-
# Command string for Spyder, fallback to script if exe not found
125-
spyder_cmd = rf'"{spyder_exe}" "%1"' if Path(spyder_exe).is_file() else rf'"{pythonw}" "{target}\Scripts\spyder" "%1"'
126-
127-
verbs_data = [
128-
# Open verb
129-
(rf"{KEY_CP}\Python.File\shell\open\command", None, rf'"{python}" "%1" %*'),
130-
(rf"{KEY_CP}\Python.NoConFile\shell\open\command", None, rf'"{pythonw}" "%1" %*'),
131-
(rf"{KEY_CP}\Python.CompiledFile\shell\open\command", None, rf'"{python}" "%1" %*'),
132-
# Edit with IDLE verb
133-
(rf"{KEY_CP}\Python.File\shell\{EWI}\command", None, rf'"{pythonw}" "{target}\Lib\idlelib\idle.pyw" -n -e "%1"'),
134-
(rf"{KEY_CP}\Python.NoConFile\shell\{EWI}\command", None, rf'"{pythonw}" "{target}\Lib\idlelib\idle.pyw" -n -e "%1"'),
135-
# Edit with Spyder verb
136-
(rf"{KEY_CP}\Python.File\shell\{EWS}\command", None, spyder_cmd),
137-
(rf"{KEY_CP}\Python.NoConFile\shell\{EWS}\command", None, spyder_cmd),
138-
]
139-
return verbs_data
140-
141-
# --- Drop support ---
142-
DROP_SUPPORT_FILE_TYPES = ["Python.File", "Python.NoConFile", "Python.CompiledFile"]
143-
for file_type in DROP_SUPPORT_FILE_TYPES:
144-
REGISTRY_ENTRIES.append((rf"{KEY_C % file_type}\shellex\DropHandler", None, DROP_HANDLER_CLSID))
145-
146-
# --- Icons ---
147-
def _get_icon_entries(target):
148-
dlls_path = str(Path(target) / "DLLs")
149-
icon_data = [
150-
(rf"{KEY_CP}\Python.File\DefaultIcon", None, rf"{dlls_path}\py.ico"),
151-
(rf"{KEY_CP}\Python.NoConFile\DefaultIcon", None, rf"{dlls_path}\py.ico"),
152-
(rf"{KEY_CP}\Python.CompiledFile\DefaultIcon", None, rf"{dlls_path}\pyc.ico"),
153-
]
154-
return icon_data
155-
156-
# --- Descriptions ---
157-
DESCRIPTIONS = {
158-
"Python.File": "Python File",
159-
"Python.NoConFile": "Python File (no console)",
160-
"Python.CompiledFile": "Compiled Python File",
161-
}
162-
for file_type, desc in DESCRIPTIONS.items():
163-
REGISTRY_ENTRIES.append((KEY_C % file_type, None, desc))
164-
165-
16681
# --- PythonCore entries (PEP-0514 and WinPython specific) ---
167-
def _get_pythoncore_entries(target):
168-
python_infos = utils.get_python_infos(target) # ('3.11', 64)
169-
short_version = python_infos[0] # e.g., '3.11'
170-
long_version = utils.get_python_long_version(target) # e.g., '3.11.5'
171-
172-
SupportUrl = "https://winpython.github.io"
173-
SysArchitecture = f'{python_infos[1]}bit' # e.g., '64bit'
174-
SysVersion = short_version # e.g., '3.11'
175-
Version = long_version # e.g., '3.11.5'
176-
DisplayName = f'Python {Version} ({SysArchitecture})'
177-
178-
python_exe = str((Path(target) / "python.exe").resolve())
179-
pythonw_exe = str((Path(target) / "pythonw.exe").resolve())
180-
181-
core_entries = []
182-
183-
# Main version key (WinPython\3.11)
184-
version_key = f"{KEY_S0}\\{short_version}"
185-
core_entries.extend([
186-
(version_key, 'DisplayName', DisplayName),
187-
(version_key, 'SupportUrl', SupportUrl),
188-
(version_key, 'SysVersion', SysVersion),
189-
(version_key, 'SysArchitecture', SysArchitecture),
190-
(version_key, 'Version', Version),
191-
])
19282

193-
# InstallPath key (WinPython\3.11\InstallPath)
194-
install_path_key = f"{version_key}\\InstallPath"
195-
core_entries.extend([
196-
(install_path_key, None, str(Path(target) / '')), # Default value is the install dir
197-
(install_path_key, 'ExecutablePath', python_exe),
198-
(install_path_key, 'WindowedExecutablePath', pythonw_exe),
199-
])
20083

201-
# InstallGroup key (WinPython\3.11\InstallPath\InstallGroup)
202-
core_entries.append((f"{install_path_key}\\InstallGroup", None, f"Python {short_version}"))
84+
def register_in_registery(target, current=True, reg_type=winreg.REG_SZ, verbose=True) -> tuple[list[any], ...]:
85+
"""Register in Windows (like regedit)"""
20386

204-
# Modules key (WinPython\3.11\Modules) - seems to be a placeholder key
205-
core_entries.append((f"{version_key}\\Modules", None, ""))
206-
207-
# PythonPath key (WinPython\3.11\PythonPath)
208-
core_entries.append((f"{version_key}\\PythonPath", None, rf"{target}\Lib;{target}\DLLs"))
209-
210-
# Help key (WinPython\3.11\Help\Main Python Documentation)
211-
core_entries.append((f"{version_key}\\Help\\Main Python Documentation", None, rf"{target}\Doc\python{long_version}.chm"))
212-
213-
return core_entries
87+
# --- Constants ---
88+
DROP_HANDLER_CLSID = "{60254CA5-953B-11CF-8C96-00AA00B8708C}"
21489

90+
# --- CONFIG ---
91+
target_path = Path(target).resolve()
92+
python_exe = str(target_path / "python.exe")
93+
pythonw_exe = str(target_path / "pythonw.exe")
94+
spyder_exe = str(target_path.parent / "Spyder.exe")
95+
icon_py = str(target / "DLLs" / "py.ico")
96+
icon_pyc = str(target / "DLLs" / "pyc.ico")
97+
idle_path = str(target / "Lib" / "idlelib" / "idle.pyw")
98+
doc_path = str(target / "Doc" / "html" / "index.html")
99+
python_infos = utils.get_python_infos(target) # ('3.11', 64)
100+
short_version = python_infos[0] # e.g., '3.11'
101+
version = utils.get_python_long_version(target) # e.g., '3.11.5'
102+
arch = f'{python_infos[1]}bit' # e.g., '64bit'
103+
display = f"Python {version} ({arch})"
104+
105+
permanent_entries = [] # key_path, name, value
106+
dynamic_entries = [] # key_path, name, value
107+
core_entries = [] # key_path, name, value
108+
lost_entries = [] # intermediate keys to remove later
109+
# --- File associations ---
110+
ext_map = {".py": "Python.File", ".pyw": "Python.NoConFile", ".pyc": "Python.CompiledFile"}
111+
ext_label = {".py": "Python File", ".pyw": "Python File (no console)", ".pyc": "Compiled Python File"}
112+
for ext, ftype in ext_map.items():
113+
permanent_entries.append((f"Software\\Classes\\{ext}", None, ftype))
114+
if ext in (".py", ".pyw"):
115+
permanent_entries.append((f"Software\\Classes\\{ext}", "Content Type", "text/plain"))
116+
117+
# --- Descriptions, Icons, DropHandlers ---
118+
for ext, ftype in ext_map.items():
119+
dynamic_entries.append((f"Software\\Classes\\{ftype}", None, ext_label[ext]))
120+
dynamic_entries.append((f"Software\\Classes\\{ftype}\\DefaultIcon", None, icon_py if "Compiled" not in ftype else icon_pyc))
121+
dynamic_entries.append((f"Software\\Classes\\{ftype}\\shellex\\DropHandler", None, DROP_HANDLER_CLSID))
122+
lost_entries.append((f"Software\\Classes\\{ftype}\\shellex", None, None))
123+
124+
# --- Shell commands ---
125+
for ext, ftype in ext_map.items():
126+
dynamic_entries.append((f"Software\\Classes\\{ftype}\\shell\\open\\command", None, f'"{pythonw_exe if ftype=='Python.NoConFile' else python_exe} if " "%1" %*'))
127+
lost_entries.append((f"Software\\Classes\\{ftype}\\shell\\open", None, None))
128+
lost_entries.append((f"Software\\Classes\\{ftype}\\shell", None, None))
129+
130+
dynamic_entries.append((rf"Software\Classes\Python.File\shell\Edit with IDLE\command", None, f'"{pythonw_exe}" "{idle_path}" -n -e "%1"'))
131+
dynamic_entries.append((rf"Software\Classes\Python.NoConFile\shell\Edit with IDLE\command", None, f'"{pythonw_exe}" "{idle_path}" -n -e "%1"'))
132+
lost_entries.append((rf"Software\Classes\Python.File\shell\Edit with IDLE", None, None))
133+
lost_entries.append((rf"Software\Classes\Python.NoConFile\shell\Edit with IDLE", None, None))
134+
135+
if Path(spyder_exe).exists():
136+
dynamic_entries.append((rf"Software\Classes\Python.File\shell\Edit with Spyder\command", None, f'"{spyder_exe}" "%1"'))
137+
dynamic_entries.append((rf"Software\Classes\Python.NoConFile\shell\Edit with Spyder\command", None, f'"{spyder_exe}" "%1"'))
138+
lost_entries.append((rf"Software\Classes\Python.File\shell\Edit with Spyder", None, None))
139+
lost_entries.append((rf"Software\Classes\Python.NoConFile\shell\Edit with Spyder", None, None))
140+
141+
# --- WinPython Core registry entries (PEP 514 style) ---
142+
base = f"Software\\Python\\WinPython\\{short_version}"
143+
core_entries.append((base, "DisplayName", display))
144+
core_entries.append((base, "SupportUrl", "https://winpython.github.io"))
145+
core_entries.append((base, "SysVersion", short_version))
146+
core_entries.append((base, "SysArchitecture", arch))
147+
core_entries.append((base, "Version", version))
148+
149+
core_entries.append((f"{base}\\InstallPath", None, str(target)))
150+
core_entries.append((f"{base}\\InstallPath", "ExecutablePath", python_exe))
151+
core_entries.append((f"{base}\\InstallPath", "WindowedExecutablePath", pythonw_exe))
152+
core_entries.append((f"{base}\\InstallPath\\InstallGroup", None, f"Python {short_version}"))
153+
154+
core_entries.append((f"{base}\\Modules", None, ""))
155+
core_entries.append((f"{base}\\PythonPath", None, f"{target}\\Lib;{target}\\DLLs"))
156+
core_entries.append((f"{base}\\Help\\Main Python Documentation", None, doc_path))
157+
lost_entries.append((f"{base}\\Help", None, None))
158+
lost_entries.append((f"Software\\Python\\WinPython", None, None))
159+
160+
return permanent_entries, dynamic_entries, core_entries, lost_entries
215161

216162
# --- Main Register/Unregister Functions ---
217163

@@ -223,19 +169,11 @@ def register(target, current=True, reg_type=winreg.REG_SZ, verbose=True):
223169
if verbose:
224170
print(f'Creating WinPython registry entries for {target}')
225171

226-
# Set static registry entries
227-
for key_path, name, value in REGISTRY_ENTRIES:
228-
_set_reg_value(root, key_path, name, value, verbose=verbose)
229-
230-
# Set dynamic registry entries (verbs, icons, pythoncore)
231-
dynamic_entries = []
232-
dynamic_entries.extend(_get_verb_entries(target))
233-
dynamic_entries.extend(_get_icon_entries(target))
234-
dynamic_entries.extend(_get_pythoncore_entries(target))
235-
236-
for key_path, name, value in dynamic_entries:
237-
_set_reg_value(root, key_path, name, value)
238-
172+
permanent_entries, dynamic_entries, core_entries, lost_entries = register_in_registery(target)
173+
# Set registry entries for given target
174+
for key_path, name, value in permanent_entries + dynamic_entries + core_entries:
175+
_set_reg_value(root, key_path, name, value, verbose=verbose)
176+
239177
# Create start menu entries
240178
if has_pywin32:
241179
if verbose:
@@ -246,8 +184,7 @@ def register(target, current=True, reg_type=winreg.REG_SZ, verbose=True):
246184
except Exception as e:
247185
print(f"Error creating shortcut for {desc} at {fname}: {e}", file=sys.stderr)
248186
else:
249-
print("Skipping start menu shortcut creation as pywin32 package is needed.")
250-
187+
print("Skipping start menu shortcut creation as pywin32 package is needed.")
251188

252189
def unregister(target, current=True, verbose=True):
253190
"""Unregister a Python distribution from Windows registry and remove Start Menu shortcuts"""
@@ -256,92 +193,26 @@ def unregister(target, current=True, verbose=True):
256193

257194
if verbose:
258195
print(f'Removing WinPython registry entries for {target}')
196+
197+
permanent_entries, dynamic_entries, core_entries , lost_entries = register_in_registery(target)
259198

260199
# List of keys to attempt to delete, ordered from most specific to general
261-
keys_to_delete = []
262-
263-
# Add dynamic keys first (helps DeleteKey succeed)
264-
dynamic_entries = []
265-
dynamic_entries.extend(_get_verb_entries(target))
266-
dynamic_entries.extend(_get_icon_entries(target))
267-
dynamic_entries.extend(_get_pythoncore_entries(target))
268-
269-
# Collect parent keys from dynamic entries
270-
dynamic_parent_keys = {entry[0] for entry in dynamic_entries}
271-
# Add keys from static entries
272-
static_parent_keys = {entry[0] for entry in REGISTRY_ENTRIES}
273-
274-
# Combine and add the key templates that might become empty and should be removed
275-
python_infos = utils.get_python_infos(target)
276-
short_version = python_infos[0]
277-
version_key_base = f"{KEY_S0}\\{short_version}"
278-
279-
# Keys from static REGISTRY_ENTRIES (mostly Class registrations)
280-
keys_to_delete.extend([
281-
KEY_C % file_type + rf"\shellex\DropHandler" for file_type in DROP_SUPPORT_FILE_TYPES
282-
])
283-
keys_to_delete.extend([
284-
KEY_C % file_type + rf"\shellex" for file_type in DROP_SUPPORT_FILE_TYPES
285-
])
286-
#keys_to_delete.extend([
287-
# KEY_C % file_type + rf"\DefaultIcon" for file_type in set(EXTENSIONS.values()) # Use values as file types
288-
#])
289-
keys_to_delete.extend([
290-
KEY_C % file_type + rf"\shell\{EWI}\command" for file_type in ["Python.File", "Python.NoConFile"] # Specific types for IDLE verb
291-
])
292-
keys_to_delete.extend([
293-
KEY_C % file_type + rf"\shell\{EWS}\command" for file_type in ["Python.File", "Python.NoConFile"] # Specific types for Spyder verb
294-
])
295-
# General open command keys (cover all file types)
296-
keys_to_delete.extend([
297-
KEY_C % file_type + rf"\shell\open\command" for file_type in ["Python.File", "Python.NoConFile", "Python.CompiledFile"]
298-
])
299-
300-
301-
# Keys from dynamic entries (Verbs, Icons, PythonCore) - add parents
302-
# Verbs
303-
keys_to_delete.extend([KEY_C % file_type + rf"\shell\{EWI}" for file_type in ["Python.File", "Python.NoConFile"]])
304-
keys_to_delete.extend([KEY_C % file_type + rf"\shell\{EWS}" for file_type in ["Python.File", "Python.NoConFile"]])
305-
keys_to_delete.extend([KEY_C % file_type + rf"\shell\open" for file_type in ["Python.File", "Python.NoConFile", "Python.CompiledFile"]])
306-
keys_to_delete.extend([KEY_C % file_type + rf"\shell" for file_type in ["Python.File", "Python.NoConFile", "Python.CompiledFile"]]) # Shell parent
307-
308-
# Icons
309-
keys_to_delete.extend([KEY_C % file_type + rf"\DefaultIcon" for file_type in set(EXTENSIONS.values())]) # Already added above? Check for duplicates or order
310-
keys_to_delete.extend([KEY_C % file_type for file_type in set(EXTENSIONS.values())]) # Parent keys for file types
311-
312-
# Extensions/Descriptions parents
313-
# keys_to_delete.extend([KEY_C % ext for ext in EXTENSIONS.keys()]) # e.g., .py, .pyw
314-
315-
# PythonCore keys (from most specific down to the base)
316-
keys_to_delete.extend([
317-
f"{version_key_base}\\InstallPath\\InstallGroup",
318-
f"{version_key_base}\\InstallPath",
319-
f"{version_key_base}\\Modules",
320-
f"{version_key_base}\\PythonPath",
321-
f"{version_key_base}\\Help\\Main Python Documentation",
322-
f"{version_key_base}\\Help",
323-
version_key_base, # e.g., Software\Python\WinPython\3.11
324-
KEY_S0, # Software\Python\WinPython
325-
#KEY_S, # Software\Python (only if WinPython key is the only subkey - risky to delete)
326-
])
327-
328-
# Attempt to delete keys
329-
# Use a set to avoid duplicates, then sort by length descending to try deleting children first
330-
# (although DeleteKey only works on empty keys anyway, so explicit ordering is clearer)
331-
332-
for key in keys_to_delete:
333-
_delete_reg_key(root, key, verbose=verbose)
200+
keys_to_delete = sorted(list(set(key_path for key_path , name, value in (dynamic_entries + core_entries + lost_entries))), key=len, reverse=True)
201+
202+
rootkey_name = "HKEY_CURRENT_USER" if root == winreg.HKEY_CURRENT_USER else "HKEY_LOCAL_MACHINE"
203+
for key_path in keys_to_delete:
204+
_delete_reg_key(root, key_path, verbose=verbose)
334205

335206
# Remove start menu shortcuts
336207
if has_pywin32:
337208
if verbose:
338209
print(f'Removing WinPython menu for all icons in {target.parent}')
339210
_remove_start_menu_folder(target, current=current, has_pywin32=True)
340211
# The original code had commented out code to delete .lnk files individually.
341-
# remove_winpython_start_menu_folder is likely the intended method.
342212
else:
343213
print("Skipping start menu removal as pywin32 package is needed.")
344214

215+
345216
if __name__ == "__main__":
346217
# Ensure we are running from the target WinPython environment
347218
parser = ArgumentParser(description="Register or Un-register Python file extensions, icons "\

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.