FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
winpython/wppm/associate.py at master · winpython/winpython · GitHub
Uh oh!
There was an error while loading.
Please reload this page
.
winpython
/
winpython
Public
Notifications
You must be signed in to change notification settings
Fork
350
Star
2.3k
Code
Issues
79
Pull requests
0
Discussions
Actions
Projects
Wiki
Security and quality
0
Insights
Additional navigation options
Code
Issues
Pull requests
Discussions
Actions
Projects
Wiki
Security and quality
Insights
Expand file tree
Breadcrumbs
winpython
/
wppm
/
associate.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
324 lines (278 loc) · 15.2 KB
Breadcrumbs
winpython
/
wppm
/
associate.py
Copy path
File metadata and controls
324 lines (278 loc) · 15.2 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# -*- coding: utf-8 -*-
#
# associate.py = Register a Python distribution
# Copyright © 2012 Pierre Raybaut
# Licensed under the terms of the MIT License
# (see winpython/__init__.py for details)
import
sys
import
os
from
pathlib
import
Path
import
importlib
.
util
import
winreg
from
.
import
utils
from
argparse
import
ArgumentParser
import
shutil
def
get_special_folder_path
(
path_name
):
"""Return special folder path."""
from
win32com
.
shell
import
shell
,
shellcon
try
:
csidl
=
getattr
(
shellcon
,
path_name
)
return
shell
.
SHGetSpecialFolderPath
(
0
,
csidl
,
False
)
except
OSError
:
print
(
f"
{
path_name
}
is an unknown path ID"
)
def
is_winpython_layout
(
target
):
"""Return True if target is the Python of a WinPython distribution (sibling 'scripts/env.bat')."""
return
(
Path
(
target
).
parent
/
"scripts"
/
"env.bat"
).
is_file
()
def
get_start_menu_folder_name
(
target
):
"""Return the Start menu folder name of a target: 'WinPython' for a WinPython
distribution, the target directory name otherwise, so that registering a plain
Python never overwrites (nor removes) the menu of another distribution."""
if
is_winpython_layout
(
target
):
return
'WinPython'
return
Path
(
target
).
resolve
().
name
or
'Python'
def
get_winpython_start_menu_folder
(
current
=
True
,
folder_name
=
'WinPython'
):
"""Return Start menu shortcuts folder of a distribution."""
folder
=
get_special_folder_path
(
"CSIDL_PROGRAMS"
)
if
not
current
:
try
:
folder
=
get_special_folder_path
(
"CSIDL_COMMON_PROGRAMS"
)
except
OSError
:
pass
return
str
(
Path
(
folder
)
/
folder_name
)
def
remove_winpython_start_menu_folder
(
current
=
True
,
folder_name
=
'WinPython'
):
"""Remove a distribution Start menu folder -- remove it if it already exists"""
path
=
get_winpython_start_menu_folder
(
current
=
current
,
folder_name
=
folder_name
)
if
Path
(
path
).
is_dir
():
try
:
shutil
.
rmtree
(
path
)
except
WindowsError
:
print
(
f"Directory
{
path
}
could not be removed"
,
file
=
sys
.
stderr
)
def
create_winpython_start_menu_folder
(
current
=
True
,
folder_name
=
'WinPython'
):
"""Create a distribution Start menu folder."""
path
=
get_winpython_start_menu_folder
(
current
=
current
,
folder_name
=
folder_name
)
if
Path
(
path
).
is_dir
():
try
:
shutil
.
rmtree
(
path
)
except
WindowsError
:
print
(
f"Directory
{
path
}
could not be removed"
,
file
=
sys
.
stderr
)
Path
(
path
).
mkdir
(
parents
=
True
,
exist_ok
=
True
)
return
path
def
create_shortcut
(
path
,
description
,
filename
,
arguments
=
""
,
workdir
=
""
,
iconpath
=
""
,
iconindex
=
0
,
verbose
=
True
):
"""Create Windows shortcut (.lnk file)."""
import
pythoncom
from
win32com
.
shell
import
shell
ilink
=
pythoncom
.
CoCreateInstance
(
shell
.
CLSID_ShellLink
,
None
,
pythoncom
.
CLSCTX_INPROC_SERVER
,
shell
.
IID_IShellLink
)
ilink
.
SetPath
(
path
)
ilink
.
SetDescription
(
description
)
if
arguments
:
ilink
.
SetArguments
(
arguments
)
if
workdir
:
ilink
.
SetWorkingDirectory
(
workdir
)
if
iconpath
or
iconindex
:
ilink
.
SetIconLocation
(
iconpath
,
iconindex
)
# now save it.
ipf
=
ilink
.
QueryInterface
(
pythoncom
.
IID_IPersistFile
)
if
not
filename
.
endswith
(
'.lnk'
):
filename
+=
'.lnk'
if
verbose
:
print
(
f'create menu *
{
filename
}
*'
)
try
:
ipf
.
Save
(
filename
,
0
)
except
:
print
(
"a fail !"
)
# --- Helper functions for Registry ---
def
_set_reg_value
(
root
,
key_path
,
name
,
value
,
reg_type
=
winreg
.
REG_SZ
,
verbose
=
False
):
"""Helper to create key and set a registry value using CreateKeyEx."""
rootkey_name
=
"HKEY_CURRENT_USER"
if
root
==
winreg
.
HKEY_CURRENT_USER
else
"HKEY_LOCAL_MACHINE"
if
verbose
:
print
(
f"
{
rootkey_name
}
\\
{
key_path
}
\\
{
name
if
name
else
''
}
:
{
value
}
"
)
try
:
# Use CreateKeyEx with context manager for automatic closing
with
winreg
.
CreateKeyEx
(
root
,
key_path
,
0
,
winreg
.
KEY_WRITE
)
as
key
:
winreg
.
SetValueEx
(
key
,
name
,
0
,
reg_type
,
value
)
except
OSError
as
e
:
print
(
f"Error creating/setting registry value
{
rootkey_name
}
\\
{
key_path
}
\\
{
name
}
:
{
e
}
"
,
file
=
sys
.
stderr
)
def
_delete_reg_key
(
root
,
key_path
,
verbose
=
False
):
"""Helper to delete a registry key, ignoring if not found."""
rootkey_name
=
"HKEY_CURRENT_USER"
if
root
==
winreg
.
HKEY_CURRENT_USER
else
"HKEY_LOCAL_MACHINE"
if
verbose
:
print
(
f"
{
rootkey_name
}
\\
{
key_path
}
"
)
try
:
# DeleteKey can only delete keys with no subkeys.
# For keys with (still) subkeys, use DeleteKeyEx on the parent key if available
winreg
.
DeleteKey
(
root
,
key_path
)
except
FileNotFoundError
:
if
verbose
:
print
(
f"Registry key not found (skipping deletion):
{
rootkey_name
}
\\
{
key_path
}
"
)
except
OSError
as
e
:
# Catch other potential errors like key not empty
print
(
f"Error deleting registry key
{
rootkey_name
}
\\
{
key_path
}
:
{
e
}
"
,
file
=
sys
.
stderr
)
# --- Helper functions for Start Menu Shortcuts ---
def
_has_pywin32
():
"""Check if pywin32 (pythoncom) is installed."""
return
importlib
.
util
.
find_spec
(
'pythoncom'
)
is
not
None
def
_remove_start_menu_folder
(
target
,
current
=
True
,
has_pywin32
=
False
):
"remove menu Folder of target distribution if pywin32 exists"
if
has_pywin32
:
remove_winpython_start_menu_folder
(
current
=
current
,
folder_name
=
get_start_menu_folder_name
(
target
))
else
:
print
(
"Skipping start menu removal as pywin32 package is not installed."
)
def
_get_launchers
(
target
):
"""Return the executables to give a Start menu shortcut to.
A WinPython distribution keeps its launchers (Spyder, Jupyter, ...) next to its
Python directory, so all of them are taken. For a plain Python, the neighbours
are unrelated files, so only its own interpreters are taken."""
if
is_winpython_layout
(
target
):
wpdir
=
Path
(
target
).
parent
return
[
wpdir
/
name
for
name
in
os
.
listdir
(
wpdir
)
if
Path
(
name
).
suffix
.
lower
()
==
".exe"
]
return
[
exe
for
exe
in
(
Path
(
target
)
/
"python.exe"
,
Path
(
target
)
/
"pythonw.exe"
)
if
exe
.
is_file
()]
def
_get_shortcut_data
(
target
,
current
=
True
,
has_pywin32
=
False
):
"get windows menu access data if pywin32 exists, otherwise empty list"
if
not
has_pywin32
:
return
[]
launchers
=
_get_launchers
(
target
)
if
not
launchers
:
return
[]
# create the menu folder once, not once per shortcut
menu_folder
=
create_winpython_start_menu_folder
(
current
=
current
,
folder_name
=
get_start_menu_folder_name
(
target
))
data
=
[]
for
exe
in
launchers
:
bname
=
exe
.
stem
data
.
append
(
(
str
(
exe
),
# Target executable path
bname
,
# Description/Name
str
(
Path
(
menu_folder
)
/
bname
)
+
'.lnk'
,
# Shortcut file path
)
)
return
data
# --- PythonCore entries (PEP-0514 and WinPython specific) ---
def
register_in_registery
(
target
,
current
=
True
,
reg_type
=
winreg
.
REG_SZ
,
verbose
=
True
)
->
tuple
[
list
[
any
], ...]:
"""Register in Windows (like regedit)"""
# --- Constants ---
DROP_HANDLER_CLSID
=
"{60254CA5-953B-11CF-8C96-00AA00B8708C}"
# --- CONFIG ---
target_path
=
Path
(
target
).
resolve
()
python_exe
=
str
(
target_path
/
"python.exe"
)
pythonw_exe
=
str
(
target_path
/
"pythonw.exe"
)
spyder_exe
=
str
(
target_path
.
parent
/
"Spyder.exe"
)
icon_py
=
str
(
target_path
/
"DLLs"
/
"py.ico"
)
icon_pyc
=
str
(
target_path
/
"DLLs"
/
"pyc.ico"
)
idle_path
=
str
(
target_path
/
"Lib"
/
"idlelib"
/
"idle.pyw"
)
doc_path
=
str
(
target_path
/
"Doc"
/
"html"
/
"index.html"
)
python_infos
=
utils
.
get_python_infos
(
target
)
# ('3.11', 64)
short_version
=
python_infos
[
0
]
# e.g., '3.11'
version
=
utils
.
get_python_long_version
(
target
)
# e.g., '3.11.5'
arch
=
f'
{
python_infos
[
1
]
}
bit'
# e.g., '64bit'
display
=
f"Python
{
version
}
(
{
arch
}
)"
permanent_entries
=
[]
# key_path, name, value
dynamic_entries
=
[]
# key_path, name, value
core_entries
=
[]
# key_path, name, value
lost_entries
=
[]
# intermediate keys to remove later
# --- File associations ---
ext_map
=
{
".py"
:
"Python.File"
,
".pyw"
:
"Python.NoConFile"
,
".pyc"
:
"Python.CompiledFile"
}
ext_label
=
{
".py"
:
"Python File"
,
".pyw"
:
"Python File (no console)"
,
".pyc"
:
"Compiled Python File"
}
for
ext
,
ftype
in
ext_map
.
items
():
permanent_entries
.
append
((
f"Software
\\
Classes
\\
{
ext
}
"
,
None
,
ftype
))
if
ext
in
(
".py"
,
".pyw"
):
permanent_entries
.
append
((
f"Software
\\
Classes
\\
{
ext
}
"
,
"Content Type"
,
"text/plain"
))
# --- Descriptions, Icons, DropHandlers ---
for
ext
,
ftype
in
ext_map
.
items
():
dynamic_entries
.
append
((
f"Software
\\
Classes
\\
{
ftype
}
"
,
None
,
ext_label
[
ext
]))
dynamic_entries
.
append
((
f"Software
\\
Classes
\\
{
ftype
}
\\
DefaultIcon"
,
None
,
icon_py
if
"Compiled"
not
in
ftype
else
icon_pyc
))
dynamic_entries
.
append
((
f"Software
\\
Classes
\\
{
ftype
}
\\
shellex
\\
DropHandler"
,
None
,
DROP_HANDLER_CLSID
))
lost_entries
.
append
((
f"Software
\\
Classes
\\
{
ftype
}
\\
shellex"
,
None
,
None
))
# --- Shell commands ---
for
ext
,
ftype
in
ext_map
.
items
():
dynamic_entries
.
append
((
f"Software
\\
Classes
\\
{
ftype
}
\\
shell
\\
open
\\
command"
,
None
,
f'''"
{
pythonw_exe
if
ftype
==
'Python.NoConFile'
else
python_exe
}
" "%1" %*'''
))
lost_entries
.
append
((
f"Software
\\
Classes
\\
{
ftype
}
\\
shell
\\
open"
,
None
,
None
))
lost_entries
.
append
((
f"Software
\\
Classes
\\
{
ftype
}
\\
shell"
,
None
,
None
))
dynamic_entries
.
append
((
rf"Software\Classes\Python.File\shell\Edit with IDLE\command"
,
None
,
f'"
{
pythonw_exe
}
" "
{
idle_path
}
" -n -e "%1"'
))
dynamic_entries
.
append
((
rf"Software\Classes\Python.NoConFile\shell\Edit with IDLE\command"
,
None
,
f'"
{
pythonw_exe
}
" "
{
idle_path
}
" -n -e "%1"'
))
lost_entries
.
append
((
rf"Software\Classes\Python.File\shell\Edit with IDLE"
,
None
,
None
))
lost_entries
.
append
((
rf"Software\Classes\Python.NoConFile\shell\Edit with IDLE"
,
None
,
None
))
if
Path
(
spyder_exe
).
exists
():
dynamic_entries
.
append
((
rf"Software\Classes\Python.File\shell\Edit with Spyder\command"
,
None
,
f'"
{
spyder_exe
}
" "%1" -w "%w"'
))
dynamic_entries
.
append
((
rf"Software\Classes\Python.NoConFile\shell\Edit with Spyder\command"
,
None
,
f'"
{
spyder_exe
}
" "%1" -w "%w"'
))
lost_entries
.
append
((
rf"Software\Classes\Python.File\shell\Edit with Spyder"
,
None
,
None
))
lost_entries
.
append
((
rf"Software\Classes\Python.NoConFile\shell\Edit with Spyder"
,
None
,
None
))
# --- WinPython Core registry entries (PEP 514 style) ---
base
=
f"Software
\\
Python
\\
WinPython
\\
{
short_version
}
"
core_entries
.
append
((
base
,
"DisplayName"
,
display
))
core_entries
.
append
((
base
,
"SupportUrl"
,
"https://winpython.github.io"
))
core_entries
.
append
((
base
,
"SysVersion"
,
short_version
))
core_entries
.
append
((
base
,
"SysArchitecture"
,
arch
))
core_entries
.
append
((
base
,
"Version"
,
version
))
core_entries
.
append
((
f"
{
base
}
\\
InstallPath"
,
None
,
str
(
target
)))
core_entries
.
append
((
f"
{
base
}
\\
InstallPath"
,
"ExecutablePath"
,
python_exe
))
core_entries
.
append
((
f"
{
base
}
\\
InstallPath"
,
"WindowedExecutablePath"
,
pythonw_exe
))
core_entries
.
append
((
f"
{
base
}
\\
InstallPath
\\
InstallGroup"
,
None
,
f"Python
{
short_version
}
"
))
core_entries
.
append
((
f"
{
base
}
\\
Modules"
,
None
,
""
))
core_entries
.
append
((
f"
{
base
}
\\
PythonPath"
,
None
,
f"
{
target
}
\\
Lib;
{
target
}
\\
DLLs"
))
core_entries
.
append
((
f"
{
base
}
\\
Help
\\
Main Python Documentation"
,
None
,
doc_path
))
lost_entries
.
append
((
f"
{
base
}
\\
Help"
,
None
,
None
))
lost_entries
.
append
((
f"Software
\\
Python
\\
WinPython"
,
None
,
None
))
return
permanent_entries
,
dynamic_entries
,
core_entries
,
lost_entries
# --- Main Register/Unregister Functions ---
def
register
(
target
,
current
=
True
,
reg_type
=
winreg
.
REG_SZ
,
verbose
=
True
):
"""Register a Python distribution in Windows registry and create Start Menu shortcuts"""
root
=
winreg
.
HKEY_CURRENT_USER
if
current
else
winreg
.
HKEY_LOCAL_MACHINE
has_pywin32
=
_has_pywin32
()
if
verbose
:
print
(
f'Creating WinPython registry entries for
{
target
}
'
)
permanent_entries
,
dynamic_entries
,
core_entries
,
lost_entries
=
register_in_registery
(
target
)
# Set registry entries for given target
for
key_path
,
name
,
value
in
permanent_entries
+
dynamic_entries
+
core_entries
:
_set_reg_value
(
root
,
key_path
,
name
,
value
,
verbose
=
verbose
)
# Create start menu entries
if
has_pywin32
:
if
verbose
:
print
(
f'Creating "
{
get_start_menu_folder_name
(
target
)
}
" start menu shortcuts'
)
for
path
,
desc
,
fname
in
_get_shortcut_data
(
target
,
current
=
current
,
has_pywin32
=
True
):
try
:
create_shortcut
(
path
,
desc
,
fname
,
verbose
=
verbose
)
except
Exception
as
e
:
print
(
f"Error creating shortcut for
{
desc
}
at
{
fname
}
:
{
e
}
"
,
file
=
sys
.
stderr
)
else
:
print
(
"Skipping start menu shortcut creation as pywin32 package is needed."
)
def
unregister
(
target
,
current
=
True
,
verbose
=
True
):
"""Unregister a Python distribution from Windows registry and remove Start Menu shortcuts"""
root
=
winreg
.
HKEY_CURRENT_USER
if
current
else
winreg
.
HKEY_LOCAL_MACHINE
has_pywin32
=
_has_pywin32
()
if
verbose
:
print
(
f'Removing WinPython registry entries for
{
target
}
'
)
permanent_entries
,
dynamic_entries
,
core_entries
,
lost_entries
=
register_in_registery
(
target
)
# List of keys to attempt to delete, ordered from most specific to general
keys_to_delete
=
sorted
(
list
(
set
(
key_path
for
key_path
,
name
,
value
in
(
dynamic_entries
+
core_entries
+
lost_entries
))),
key
=
len
,
reverse
=
True
)
rootkey_name
=
"HKEY_CURRENT_USER"
if
root
==
winreg
.
HKEY_CURRENT_USER
else
"HKEY_LOCAL_MACHINE"
for
key_path
in
keys_to_delete
:
_delete_reg_key
(
root
,
key_path
,
verbose
=
verbose
)
# Remove start menu shortcuts
if
has_pywin32
:
if
verbose
:
print
(
f'Removing "
{
get_start_menu_folder_name
(
target
)
}
" start menu shortcuts'
)
_remove_start_menu_folder
(
target
,
current
=
current
,
has_pywin32
=
True
)
# The original code had commented out code to delete .lnk files individually.
else
:
print
(
"Skipping start menu removal as pywin32 package is needed."
)
if
__name__
==
"__main__"
:
# Ensure we are running from the target WinPython environment
parser
=
ArgumentParser
(
description
=
"Register or Un-register Python file extensions, icons "
\
"and Windows explorer context menu to this "
\
"Python distribution."
)
parser
.
add_argument
(
'--unregister'
,
action
=
"store_true"
,
help
=
'register to all users, requiring administrative '
\
'privileges (default: register to current user only)'
)
parser
.
add_argument
(
'--all'
,
action
=
"store_true"
,
help
=
'action is to all users, requiring administrative '
\
'privileges (default: to current user only)'
)
args
=
parser
.
parse_args
()
expected_target
=
Path
(
sys
.
prefix
)
command
=
"unregister"
if
args
.
unregister
else
"register"
users
=
"all"
if
args
.
all
else
"user"
print
(
f"Attempting to
{
command
}
the Python environment for
{
users
}
at:
{
expected_target
}
"
)
target_dir
=
sys
.
prefix
# Or get from arguments
is_current_user
=
True
# Or get from arguments
if
command
==
"register"
:
register
(
expected_target
,
current
=
not
args
.
all
)
else
:
unregister
(
expected_target
,
current
=
not
args
.
all
)
Back
|
FazBrowse Home
|
New Git URL