Skip to content

Commit 73f0f69

Browse files
committed
Ensure Start shortcuts and file associations use good working directory.
For associations, this requires adding a (private) argument to the commands that will reinterpret the CWD. This argument is applied prior to launching the script, which may result in the script not being found. However, for the relevant cases, a full path is always passed for the script and this shouldn't happen. Fixes #134
1 parent 52aa23c commit 73f0f69

File tree

8 files changed

+158
-81
lines changed

8 files changed

+158
-81
lines changed

_msbuild.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class ResourceFile(CSourceFile):
7878
CFunction('file_locked_delete'),
7979
CFunction('package_get_root'),
8080
CFunction('shortcut_create'),
81+
CFunction('shortcut_default_cwd'),
8182
CFunction('shortcut_get_start_programs'),
8283
CFunction('hide_file'),
8384
CFunction('fd_supports_vt100'),
@@ -97,7 +98,10 @@ def main_exe(name):
9798
VersionInfo(FileDescription="Python Install Manager"),
9899
CPP_SETTINGS,
99100
ItemDefinition('ClCompile', PreprocessorDefinitions=Prepend(f'EXE_NAME=L"{name}";')),
100-
ItemDefinition('Link', SubSystem='CONSOLE', DelayLoadDLLs=f"{DLL_NAME}.dll"),
101+
ItemDefinition('Link',
102+
SubSystem='CONSOLE',
103+
DelayLoadDLLs=f'{DLL_NAME}.dll;ole32.dll;shell32.dll;advapi32.dll',
104+
),
101105
INCLUDE_TMPDIR,
102106
Manifest('default.manifest'),
103107
ResourceFile('pyicon.rc'),
@@ -115,7 +119,10 @@ def mainw_exe(name):
115119
return CProject(name,
116120
VersionInfo(FileDescription="Python Install Manager (windowed)"),
117121
CPP_SETTINGS,
118-
ItemDefinition('Link', SubSystem='WINDOWS', DelayLoadDLLs=f"{DLL_NAME}.dll"),
122+
ItemDefinition('Link',
123+
SubSystem='WINDOWS',
124+
DelayLoadDLLs=f'{DLL_NAME}.dll;ole32.dll;shell32.dll;advapi32.dll',
125+
),
119126
INCLUDE_TMPDIR,
120127
ItemDefinition('ClCompile', PreprocessorDefinitions=Prepend(f'EXE_NAME=L"{name}";')),
121128
ItemDefinition('ClCompile', PreprocessorDefinitions=Prepend("PY_WINDOWED=1;")),

_msbuild_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
CFunction('file_locked_delete'),
4545
CFunction('package_get_root'),
4646
CFunction('shortcut_create'),
47+
CFunction('shortcut_default_cwd'),
4748
CFunction('shortcut_get_start_programs'),
4849
CFunction('hide_file'),
4950
CFunction('fd_supports_vt100'),

src/_native/shortcut.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <windows.h>
33
#include <shlobj.h>
44
#include <shlguid.h>
5+
#include <iterator>
56
#include "helpers.h"
67

78
extern "C" {
@@ -111,6 +112,27 @@ shortcut_get_start_programs(PyObject *, PyObject *, PyObject *)
111112
}
112113

113114

115+
PyObject *
116+
shortcut_default_cwd(PyObject *, PyObject *, PyObject *)
117+
{
118+
static const KNOWNFOLDERID fids[] = { FOLDERID_Documents, FOLDERID_Profile };
119+
for (auto i = std::begin(fids); i != std::end(fids); ++i) {
120+
wchar_t *path;
121+
if (SUCCEEDED(SHGetKnownFolderPath(
122+
*i,
123+
KF_FLAG_NO_PACKAGE_REDIRECTION,
124+
NULL,
125+
&path
126+
))) {
127+
PyObject *r = PyUnicode_FromWideChar(path, -1);
128+
CoTaskMemFree(path);
129+
return r;
130+
}
131+
}
132+
return Py_GetConstant(Py_CONSTANT_NONE);
133+
}
134+
135+
114136
PyObject *
115137
hide_file(PyObject *, PyObject *args, PyObject *kwargs)
116138
{

src/manage/commands.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939

4040

4141
WELCOME = f"""!B!Python install manager was successfully updated to {__version__}.!W!
42+
!Y!Start menu shortcuts have been changed in this update.!W!
43+
Run !G!py install --refresh!W! to update any existing shortcuts.
4244
"""
4345

4446
# The 'py help' or 'pymanager help' output is constructed by these default docs,

src/manage/startutils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ def _make(root, prefix, item, allow_warn=True):
5151
lnk,
5252
target,
5353
arguments=_unprefix(item.get("Arguments"), prefix),
54-
working_directory=_unprefix(item.get("WorkingDirectory"), prefix),
54+
working_directory=_unprefix(item.get("WorkingDirectory"), prefix)
55+
or _native.shortcut_default_cwd(),
5556
icon=_unprefix(item.get("Icon"), prefix),
5657
icon_index=item.get("IconIndex", 0),
5758
)

src/pymanager/appxmanifest.xml

Lines changed: 72 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
99
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
1010
xmlns:desktop7="http://schemas.microsoft.com/appx/manifest/desktop/windows10/7"
11+
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
1112
xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4"
1213
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
1314
xmlns:uap13="http://schemas.microsoft.com/appx/manifest/uap/windows10/13"
@@ -108,6 +109,53 @@
108109
<uap5:ExecutionAlias Alias="py.exe" />
109110
</uap5:AppExecutionAlias>
110111
</uap5:Extension>
112+
<uap3:Extension Category="windows.fileTypeAssociation">
113+
<uap3:FileTypeAssociation Name="py" Parameters="exec --__fix-cwd &quot;%1&quot; %*">
114+
<uap:DisplayName>Python Script</uap:DisplayName>
115+
<uap:InfoTip>Python Script</uap:InfoTip>
116+
<uap:Logo>_resources/pyx256.png</uap:Logo>
117+
<desktop7:Logo>_resources/py.ico</desktop7:Logo>
118+
<uap:EditFlags OpenIsSafe="false" />
119+
<uap:SupportedFileTypes>
120+
<uap:FileType ContentType="text/x-python"
121+
uap4:ShellNewFileName="templates\template.py">.py</uap:FileType>
122+
</uap:SupportedFileTypes>
123+
</uap3:FileTypeAssociation>
124+
</uap3:Extension>
125+
<uap3:Extension Category="windows.fileTypeAssociation">
126+
<uap3:FileTypeAssociation Name="pyc" Parameters="exec --__fix-cwd &quot;%1&quot; %*">
127+
<uap:DisplayName>Compiled Python Script</uap:DisplayName>
128+
<uap:InfoTip>Compiled Python Script</uap:InfoTip>
129+
<desktop7:Logo>_resources/pyc.ico</desktop7:Logo>
130+
<uap:EditFlags OpenIsSafe="false" AlwaysUnsafe="true" />
131+
<uap:SupportedFileTypes>
132+
<uap:FileType ContentType="application/x-python-code">.pyc</uap:FileType>
133+
</uap:SupportedFileTypes>
134+
</uap3:FileTypeAssociation>
135+
</uap3:Extension>
136+
<uap3:Extension Category="windows.fileTypeAssociation">
137+
<uap3:FileTypeAssociation Name="pyd">
138+
<uap:DisplayName>Python Extension Module</uap:DisplayName>
139+
<uap:InfoTip>Python Extension Module</uap:InfoTip>
140+
<desktop7:Logo>_resources/pyd.ico</desktop7:Logo>
141+
<uap:EditFlags OpenIsSafe="false" AlwaysUnsafe="true" />
142+
<uap:SupportedFileTypes>
143+
<uap:FileType ContentType="application/x-python-pyd">.pyd</uap:FileType>
144+
</uap:SupportedFileTypes>
145+
</uap3:FileTypeAssociation>
146+
</uap3:Extension>
147+
<uap3:Extension Category="windows.fileTypeAssociation">
148+
<uap3:FileTypeAssociation Name="pyz" Parameters="exec --__fix-cwd &quot;%1&quot; %*">
149+
<uap:DisplayName>Python Application</uap:DisplayName>
150+
<uap:InfoTip>Python Application</uap:InfoTip>
151+
<uap:Logo>_resources/pythonx150.png</uap:Logo>
152+
<desktop7:Logo>_resources/python.ico</desktop7:Logo>
153+
<uap:EditFlags OpenIsSafe="false" />
154+
<uap:SupportedFileTypes>
155+
<uap:FileType ContentType="application/x-python-zipapp">.pyz</uap:FileType>
156+
</uap:SupportedFileTypes>
157+
</uap3:FileTypeAssociation>
158+
</uap3:Extension>
111159
</Extensions>
112160
</Application>
113161
<Application Id="Pyw.Exe"
@@ -127,6 +175,30 @@
127175
<uap5:ExecutionAlias Alias="pyw.exe" />
128176
</uap5:AppExecutionAlias>
129177
</uap5:Extension>
178+
<uap3:Extension Category="windows.fileTypeAssociation">
179+
<uap3:FileTypeAssociation Name="pyw" Parameters="exec --__fix-cwd &quot;%1&quot; %*">
180+
<uap:DisplayName>Python Script (Windowed)</uap:DisplayName>
181+
<uap:InfoTip>Python Script (Windowed)</uap:InfoTip>
182+
<uap:Logo>_resources/pyx256.png</uap:Logo>
183+
<desktop7:Logo>_resources/py.ico</desktop7:Logo>
184+
<uap:EditFlags OpenIsSafe="false" />
185+
<uap:SupportedFileTypes>
186+
<uap:FileType ContentType="text/x-python">.pyw</uap:FileType>
187+
</uap:SupportedFileTypes>
188+
</uap3:FileTypeAssociation>
189+
</uap3:Extension>
190+
<uap3:Extension Category="windows.fileTypeAssociation">
191+
<uap3:FileTypeAssociation Name="pyzw" Parameters="exec --__fix-cwd &quot;%1&quot; %*">
192+
<uap:DisplayName>Python Application (Windowed)</uap:DisplayName>
193+
<uap:InfoTip>Python Application (Windowed)</uap:InfoTip>
194+
<uap:Logo>_resources/pythonwx150.png</uap:Logo>
195+
<desktop7:Logo>_resources/pythonw.ico</desktop7:Logo>
196+
<uap:EditFlags OpenIsSafe="false" />
197+
<uap:SupportedFileTypes>
198+
<uap:FileType ContentType="application/x-python-zipapp">.pyzw</uap:FileType>
199+
</uap:SupportedFileTypes>
200+
</uap3:FileTypeAssociation>
201+
</uap3:Extension>
130202
</Extensions>
131203
</Application>
132204

@@ -147,53 +219,6 @@
147219
<uap5:ExecutionAlias Alias="python.exe" />
148220
</uap5:AppExecutionAlias>
149221
</uap5:Extension>
150-
<uap:Extension Category="windows.fileTypeAssociation">
151-
<uap:FileTypeAssociation Name="py">
152-
<uap:DisplayName>Python Script</uap:DisplayName>
153-
<uap:InfoTip>Python Script</uap:InfoTip>
154-
<uap:Logo>_resources/pyx256.png</uap:Logo>
155-
<desktop7:Logo>_resources/py.ico</desktop7:Logo>
156-
<uap:EditFlags OpenIsSafe="false" />
157-
<uap:SupportedFileTypes>
158-
<uap:FileType ContentType="text/x-python"
159-
uap4:ShellNewFileName="templates\template.py">.py</uap:FileType>
160-
</uap:SupportedFileTypes>
161-
</uap:FileTypeAssociation>
162-
</uap:Extension>
163-
<uap:Extension Category="windows.fileTypeAssociation">
164-
<uap:FileTypeAssociation Name="pyc">
165-
<uap:DisplayName>Compiled Python Script</uap:DisplayName>
166-
<uap:InfoTip>Compiled Python Script</uap:InfoTip>
167-
<desktop7:Logo>_resources/pyc.ico</desktop7:Logo>
168-
<uap:EditFlags OpenIsSafe="false" AlwaysUnsafe="true" />
169-
<uap:SupportedFileTypes>
170-
<uap:FileType ContentType="application/x-python-code">.pyc</uap:FileType>
171-
</uap:SupportedFileTypes>
172-
</uap:FileTypeAssociation>
173-
</uap:Extension>
174-
<uap:Extension Category="windows.fileTypeAssociation">
175-
<uap:FileTypeAssociation Name="pyd">
176-
<uap:DisplayName>Python Extension Module</uap:DisplayName>
177-
<uap:InfoTip>Python Extension Module</uap:InfoTip>
178-
<desktop7:Logo>_resources/pyd.ico</desktop7:Logo>
179-
<uap:EditFlags OpenIsSafe="false" AlwaysUnsafe="true" />
180-
<uap:SupportedFileTypes>
181-
<uap:FileType ContentType="application/x-python-pyd">.pyd</uap:FileType>
182-
</uap:SupportedFileTypes>
183-
</uap:FileTypeAssociation>
184-
</uap:Extension>
185-
<uap:Extension Category="windows.fileTypeAssociation">
186-
<uap:FileTypeAssociation Name="pyz">
187-
<uap:DisplayName>Python Application</uap:DisplayName>
188-
<uap:InfoTip>Python Application</uap:InfoTip>
189-
<uap:Logo>_resources/pythonx150.png</uap:Logo>
190-
<desktop7:Logo>_resources/python.ico</desktop7:Logo>
191-
<uap:EditFlags OpenIsSafe="false" />
192-
<uap:SupportedFileTypes>
193-
<uap:FileType ContentType="application/x-python-zipapp">.pyz</uap:FileType>
194-
</uap:SupportedFileTypes>
195-
</uap:FileTypeAssociation>
196-
</uap:Extension>
197222
<desktop4:Extension Category="windows.fileExplorerContextMenus">
198223
<desktop4:FileExplorerContextMenus>
199224
<desktop4:ItemType Type=".py">
@@ -248,30 +273,6 @@
248273
<uap5:ExecutionAlias Alias="pythonw.exe" />
249274
</uap5:AppExecutionAlias>
250275
</uap5:Extension>
251-
<uap:Extension Category="windows.fileTypeAssociation">
252-
<uap:FileTypeAssociation Name="pyw">
253-
<uap:DisplayName>Python Script (Windowed)</uap:DisplayName>
254-
<uap:InfoTip>Python Script (Windowed)</uap:InfoTip>
255-
<uap:Logo>_resources/pyx256.png</uap:Logo>
256-
<desktop7:Logo>_resources/py.ico</desktop7:Logo>
257-
<uap:EditFlags OpenIsSafe="false" />
258-
<uap:SupportedFileTypes>
259-
<uap:FileType ContentType="text/x-python">.pyw</uap:FileType>
260-
</uap:SupportedFileTypes>
261-
</uap:FileTypeAssociation>
262-
</uap:Extension>
263-
<uap:Extension Category="windows.fileTypeAssociation">
264-
<uap:FileTypeAssociation Name="pyzw">
265-
<uap:DisplayName>Python Application (Windowed)</uap:DisplayName>
266-
<uap:InfoTip>Python Application (Windowed)</uap:InfoTip>
267-
<uap:Logo>_resources/pythonwx150.png</uap:Logo>
268-
<desktop7:Logo>_resources/pythonw.ico</desktop7:Logo>
269-
<uap:EditFlags OpenIsSafe="false" />
270-
<uap:SupportedFileTypes>
271-
<uap:FileType ContentType="application/x-python-zipapp">.pyzw</uap:FileType>
272-
</uap:SupportedFileTypes>
273-
</uap:FileTypeAssociation>
274-
</uap:Extension>
275276
</Extensions>
276277
</Application>
277278
</Applications>

src/pymanager/main.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <string.h>
33

44
#include <Windows.h>
5+
#include <shlwapi.h>
56
#include <stdio.h>
67

78
#include <shellapi.h>
@@ -483,6 +484,36 @@ locate_runtime(
483484
}
484485

485486

487+
static int
488+
fix_working_directory(const std::wstring &script)
489+
{
490+
HRESULT hr;
491+
// If we have a script, use its parent directory
492+
if (!script.empty()) {
493+
auto end = script.find_last_of(L"/\\");
494+
if (end != script.npos) {
495+
std::wstring current_dir(script.data(), end);
496+
SetCurrentDirectoryW(current_dir.c_str());
497+
return 0;
498+
}
499+
}
500+
// If we have no script, assume the user's documents folder
501+
wchar_t *path;
502+
if (SUCCEEDED(hr = SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &path))) {
503+
SetCurrentDirectoryW(path);
504+
CoTaskMemFree(path);
505+
return 0;
506+
}
507+
// As a fallback, use the user's profile (e.g. for SYSTEM)
508+
if (SUCCEEDED(hr = SHGetKnownFolderPath(FOLDERID_Profile, 0, NULL, &path))) {
509+
SetCurrentDirectoryW(path);
510+
CoTaskMemFree(path);
511+
return 0;
512+
}
513+
return hr;
514+
}
515+
516+
486517
int
487518
wmain(int argc, wchar_t **argv)
488519
{
@@ -504,6 +535,7 @@ wmain(int argc, wchar_t **argv)
504535

505536
const wchar_t *default_cmd;
506537
bool use_commands, use_cli_tag, use_shebangs, use_autoinstall;
538+
bool fix_cwd = false;
507539
per_exe_settings(argc, argv, &default_cmd, &use_commands, &use_cli_tag, &use_shebangs, &use_autoinstall);
508540

509541
if (use_commands) {
@@ -519,6 +551,10 @@ wmain(int argc, wchar_t **argv)
519551
// We handle 'exec' in native code, so it won't be in the above list
520552
if (!wcscmp(argv[1], L"exec")) {
521553
skip_argc += 1;
554+
if (!wcscmp(argv[2], L"--__fix-cwd")) {
555+
fix_cwd = true;
556+
skip_argc += 1;
557+
}
522558
use_cli_tag = argc >= 3;
523559
use_shebangs = argc >= 3;
524560
default_cmd = NULL;
@@ -570,6 +606,13 @@ wmain(int argc, wchar_t **argv)
570606
// Theoretically shouldn't matter, but might help reduce memory usage.
571607
close_python();
572608

609+
if (fix_cwd) {
610+
err = fix_working_directory(script);
611+
if (err) {
612+
fprintf(stderr, "[WARN] Failed to fix working directory (0x%08X).\n", err);
613+
}
614+
}
615+
573616
err = launch(executable.c_str(), args.c_str(), skip_argc, &exitCode);
574617

575618
// TODO: Consider sharing print_error() with launcher.cpp

0 commit comments

Comments
 (0)