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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy