Skip to content

Commit 2f864b7

Browse files
authored
Ensure Start shortcuts and file associations use good working directory. (#136)
Fixes #134
1 parent 5c71dfa commit 2f864b7

File tree

6 files changed

+94
-3
lines changed

6 files changed

+94
-3
lines changed

_msbuild.py

Lines changed: 11 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,11 @@ 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+
DisableSpecificWarnings=Prepend('4199;'),
105+
),
101106
INCLUDE_TMPDIR,
102107
Manifest('default.manifest'),
103108
ResourceFile('pyicon.rc'),
@@ -115,7 +120,11 @@ def mainw_exe(name):
115120
return CProject(name,
116121
VersionInfo(FileDescription="Python Install Manager (windowed)"),
117122
CPP_SETTINGS,
118-
ItemDefinition('Link', SubSystem='WINDOWS', DelayLoadDLLs=f"{DLL_NAME}.dll"),
123+
ItemDefinition('Link',
124+
SubSystem='WINDOWS',
125+
DelayLoadDLLs=f'{DLL_NAME}.dll;ole32.dll;shell32.dll;advapi32.dll',
126+
DisableSpecificWarnings=Prepend('4199;'),
127+
),
119128
INCLUDE_TMPDIR,
120129
ItemDefinition('ClCompile', PreprocessorDefinitions=Prepend(f'EXE_NAME=L"{name}";')),
121130
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/main.cpp

Lines changed: 56 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>
@@ -23,6 +24,11 @@
2324
#define PY_WINDOWED 0
2425
#endif
2526

27+
// Uncomment to add the --__fix-cwd private argument, which will reset the
28+
// working directory to the script location or user documents/profile before
29+
// running Python.
30+
//#define ENABLE_FIX_CWD
31+
2632
struct {
2733
PyObject *mod;
2834
PyObject *no_install_found_error;
@@ -483,6 +489,38 @@ locate_runtime(
483489
}
484490

485491

492+
#ifdef ENABLE_FIX_CWD
493+
static int
494+
fix_working_directory(const std::wstring &script)
495+
{
496+
HRESULT hr;
497+
// If we have a script, use its parent directory
498+
if (!script.empty()) {
499+
auto end = script.find_last_of(L"/\\");
500+
if (end != script.npos) {
501+
std::wstring current_dir(script.data(), end);
502+
SetCurrentDirectoryW(current_dir.c_str());
503+
return 0;
504+
}
505+
}
506+
// If we have no script, assume the user's documents folder
507+
wchar_t *path;
508+
if (SUCCEEDED(hr = SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &path))) {
509+
SetCurrentDirectoryW(path);
510+
CoTaskMemFree(path);
511+
return 0;
512+
}
513+
// As a fallback, use the user's profile (e.g. for SYSTEM)
514+
if (SUCCEEDED(hr = SHGetKnownFolderPath(FOLDERID_Profile, 0, NULL, &path))) {
515+
SetCurrentDirectoryW(path);
516+
CoTaskMemFree(path);
517+
return 0;
518+
}
519+
return hr;
520+
}
521+
#endif
522+
523+
486524
int
487525
wmain(int argc, wchar_t **argv)
488526
{
@@ -504,6 +542,9 @@ wmain(int argc, wchar_t **argv)
504542

505543
const wchar_t *default_cmd;
506544
bool use_commands, use_cli_tag, use_shebangs, use_autoinstall;
545+
#ifdef ENABLE_FIX_CWD
546+
bool fix_cwd = false;
547+
#endif
507548
per_exe_settings(argc, argv, &default_cmd, &use_commands, &use_cli_tag, &use_shebangs, &use_autoinstall);
508549

509550
if (use_commands) {
@@ -519,6 +560,12 @@ wmain(int argc, wchar_t **argv)
519560
// We handle 'exec' in native code, so it won't be in the above list
520561
if (!wcscmp(argv[1], L"exec")) {
521562
skip_argc += 1;
563+
#ifdef ENABLE_FIX_CWD
564+
if (!wcscmp(argv[2], L"--__fix-cwd")) {
565+
fix_cwd = true;
566+
skip_argc += 1;
567+
}
568+
#endif
522569
use_cli_tag = argc >= 3;
523570
use_shebangs = argc >= 3;
524571
default_cmd = NULL;
@@ -570,6 +617,15 @@ wmain(int argc, wchar_t **argv)
570617
// Theoretically shouldn't matter, but might help reduce memory usage.
571618
close_python();
572619

620+
#ifdef ENABLE_FIX_CWD
621+
if (fix_cwd) {
622+
err = fix_working_directory(script);
623+
if (err) {
624+
fprintf(stderr, "[WARN] Failed to fix working directory (0x%08X).\n", err);
625+
}
626+
}
627+
#endif
628+
573629
err = launch(executable.c_str(), args.c_str(), skip_argc, &exitCode);
574630

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

0 commit comments

Comments
 (0)