From 36d116e24325602f0d01968468bddee58fbf0534 Mon Sep 17 00:00:00 2001 From: Rainer Kottenhoff Date: Mon, 23 Feb 2026 13:59:54 +0100 Subject: [PATCH] fix: INI file handling Notepad3 and Minipath --- .github/copilot-instructions.md | 1 + CLAUDE.md | 162 ++++++++++++++++++ minipath/src/Config.cpp | 38 ++-- .../App/Notepad3/x64/np3/Notepad3.ini | 2 + .../App/Notepad3/x86/np3/Notepad3.ini | 2 + src/Config/Config.cpp | 36 ++-- src/PathLib.c | 101 ++++++++++- 7 files changed, 318 insertions(+), 24 deletions(-) create mode 100644 CLAUDE.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3ae38ed89..3e78d4126 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -156,3 +156,4 @@ Notepad3 follows a **portable-app** design for its configuration file (`Notepad3 - **Admin redirect**: An administrator can place `Notepad3.ini=` in `[Notepad3]` section of the app-directory INI to redirect to a per-user path. Up to 2 levels of redirect are supported. Redirect targets **are** auto-created (the admin intended them to exist). - **Key paths**: `Paths.IniFile` = active writable INI (empty if none exists), `Paths.IniFileDefault` = fallback path for "Save Settings Now" recovery. - **Configuration code**: All INI init logic lives in `src\Config\Config.cpp` — `FindIniFile()` → `TestIniFile()` → `CreateIniFile()` → `LoadSettings()`. +- **MiniPath** follows the same portable INI and admin-redirect pattern (`minipath\src\Config.cpp`). Redirect targets are auto-created via `CreateIniFileEx()`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..fd79a1374 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,162 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Notepad3 is a Windows-only Win32 desktop text editor (C/C++) built on **Scintilla** (editing component) and **Lexilla** (syntax highlighting). It ships with companion tools **MiniPath** (file browser, Ctrl+M) and **grepWinNP3** (file search/grep, Ctrl+Shift+F). Licensed under BSD 3-Clause. + +## Build Commands + +```powershell +# NuGet restore (required before first build) +nuget restore Notepad3.sln + +# Single platform builds +Build\Build_x64.cmd [Release|Debug] +Build\Build_Win32.cmd [Release|Debug] +Build\Build_ARM64.cmd [Release|Debug] +Build\Build_x64_AVX2.cmd [Release|Debug] + +# All platforms at once +Build\BuildAll.cmd [Release|Debug] + +# MSBuild directly (used by CI) +msbuild Notepad3.sln /m /p:Configuration=Release /p:Platform=x64 + +# Clean all outputs +Build\Clean.cmd +``` + +Default configuration is Release. Build scripts delegate to PowerShell in `Build\scripts\`. + +### Versioning + +Run `Version.ps1` before building to generate `src\VersionEx.h` from templates in `Versions\`. Format: `Major.YY.Mdd.Build` (build number persisted in `Versions\build.txt`). + +### Tests + +```cmd +cd test +TestFileVersion.cmd # Verifies built binary version info +TestAhkNotepad3.cmd # AutoHotkey-based GUI tests (requires AutoHotkey) +``` + +### CI + +GitHub Actions (`.github/workflows/build.yml`) builds all four platforms (Win32, x64, x64_AVX2, ARM64) in Release on `windows-2022` runners, triggered on push/PR to master. + +## Architecture + +### Core Modules (`src\`) + +| File | Purpose | +|------|---------| +| **Notepad3.c/h** | Entry point (`wWinMain`), window procedure (`MainWndProc`), global state structs (`Globals`, `Settings`, `Settings2`, `Flags`, `Paths`) | +| **Edit.c/h** | Text manipulation: find/replace (Oniguruma regex), encoding conversion, clipboard, indentation, sorting, bookmarks, folding, auto-complete | +| **Styles.c/h** | Scintilla styling, lexer selection, theme management, margin configuration | +| **Dialogs.c/h** | All dialog boxes, DPI-aware UI interactions, window placement | +| **Config/Config.cpp/h** | INI file management, settings loading/saving, MRU list | +| **Encoding.c/h** | Encoding detection and conversion (integrates uchardet) | +| **SciCall.h** | Type-safe inline wrappers for Scintilla direct function calls (avoids `SendMessage` overhead) | +| **DynStrg.c/h** | Custom dynamic wide-string type (`HSTRINGW`) with automatic buffer management | +| **PathLib.c/h** | Path manipulation via opaque `HPATHL` handle | +| **TypeDefs.h** | Core type definitions (`DocPos`, `DocLn`, `cpi_enc_t`), Windows version targeting, compiler macros | +| **MuiLanguage.c/h** | Multi-language UI support, language DLL loading | + +### Window Hierarchy + +``` +MainWndProc (Notepad3.c) + +-- Scintilla Edit Control (hwndEdit / IDC_EDIT) + +-- Toolbar (via Rebar control) + +-- Status Bar (16 configurable fields) +``` + +### Vendored Dependencies + +| Directory | Library | Purpose | +|-----------|---------|---------| +| `scintilla\` | Scintilla 5.5.8 | Editor component (NP3 patches in `np3_patches\`, docs in `doc\`) | +| `lexilla\` | Lexilla 5.4.6 | Syntax highlighting (NP3 patches in `np3_patches\`, docs in `doc\`) | +| `scintilla\oniguruma\` | Oniguruma | Regex engine for find/replace | +| `src\uchardet\` | uchardet | Mozilla encoding detection | +| `src\tinyexpr\` / `src\tinyexprcpp\` | TinyExpr | Expression evaluator (statusbar) | +| `src\uthash\` | uthash | Hash table / dynamic array macros | +| `src\crypto\` | Rijndael/SHA-256 | AES-256 encryption | +| Boost (via `vcpkg.json`) | Boost Regex & IOStreams | Used by grepWinNP3 | + +### Syntax Lexers (`src\StyleLexers\`) + +50+ languages, each in a `styleLexXXX.c` file. All follow the `EDITLEXER` struct pattern from `EditLexer.h`: + +```c +EDITLEXER lexXXX = { + SCLEX_XXX, // Scintilla lexer ID + "lexerName", // Lexilla lexer name (case-sensitive) + IDS_LEX_XXX_STR, // Resource string ID + L"Config Name", // INI section name + L"ext1; ext2", // Default file extensions + L"", // Extension buffer (runtime) + &KeyWords_XXX, // Keyword lists + { /* EDITSTYLE array */ } +}; +``` + +To add a new lexer: create `styleLexNEW.c`, define the `EDITLEXER` struct, register in `Styles.c` lexer array, add localization string IDs to resource files. + +### Localization (`language\`) + +Resource-based MUI system with 27+ locales. Each locale has a `np3_LANG_COUNTRY\` directory with `.rc` files. Language packs are built as separate DLLs (separate projects in the solution). + +### Configuration / Portable Design + +- INI file alongside executable (`Notepad3.ini`), no registry usage +- **No auto-creation**: runs with defaults if no INI exists; user explicitly creates via "Save Settings Now" +- **Admin redirect**: `Notepad3.ini=` in `[Notepad3]` section redirects to per-user path (up to 2 levels) +- Key paths: `Paths.IniFile` (active writable INI), `Paths.IniFileDefault` (fallback for recovery) +- INI init flow: `FindIniFile()` -> `TestIniFile()` -> `CreateIniFile()` -> `LoadSettings()` +- **MiniPath** follows the same portable INI and admin-redirect pattern (`minipath\src\Config.cpp`). Redirect targets are auto-created via `CreateIniFileEx()`. + +### DarkMode (`src\DarkMode\`) + +Windows 10/11 dark mode via IAT (Import Address Table) hooks. Includes stub DLLs for uxtheme and user32. + +## Code Conventions + +### Formatting + +- LLVM-based `.clang-format` in `src\` — 4-space indentation, Stroustrup brace style, left-aligned pointers, no column limit, no include sorting +- `.editorconfig` enforces UTF-8/CRLF for source, 4-space indent for C/C++; Lexilla code uses tabs (preserved from upstream) +- String safety via `strsafe.h` throughout; deprecated string functions are disabled + +### Type Conventions + +- `DocPos` / `DocPosU` / `DocLn` for Scintilla document positions and line numbers (not raw `int`) +- `cpi_enc_t` for encoding identifiers +- `HSTRINGW` and `HPATHL` (opaque handle types) instead of raw `WCHAR*` buffers +- `NOMINMAX` is defined globally — use `min()`/`max()` macros or typed equivalents + +### Scintilla Interaction + +Always use `SciCall.h` wrappers (e.g. `SciCall_GetTextLength()`) instead of raw `SendMessage(hwnd, SCI_XXX, ...)`. Add missing wrappers to `SciCall.h` if needed. + +Wrapper macros follow the naming `DeclareSciCall{V|R}{0|01|1|2}`: +- **V** = void return, **R** = has return value +- **0** = no params, **1** = one param (wParam), **2** = two params, **01** = lParam only (wParam=0) +- The `msg` argument is the suffix after `SCI_` (e.g. `UNDO` for `SCI_UNDO`) + +```c +DeclareSciCallV0(Undo, UNDO); // SciCall_Undo() +DeclareSciCallR0(GetTextLength, GETTEXTLENGTH, DocPos); // DocPos SciCall_GetTextLength() +DeclareSciCallV1(SetTechnology, SETTECHNOLOGY, int, technology); // SciCall_SetTechnology(int) +DeclareSciCallR1(SupportsFeature, SUPPORTSFEATURE, bool, int, feature); // bool SciCall_SupportsFeature(int) +``` + +### Global State + +Application state is centralized in global structs (`Globals`, `Settings`, `Settings2`, `Flags`, `Paths`) defined in `Notepad3.c`. Access these through their defined interfaces rather than adding new globals. + +### Undo/Redo Transactions + +Use `_BEGIN_UNDO_ACTION_` / `_END_UNDO_ACTION_` macros (defined in `Notepad3.h`) to group Scintilla operations into single undo steps. These also handle notification limiting during bulk edits. diff --git a/minipath/src/Config.cpp b/minipath/src/Config.cpp index d1db44b83..bc43838e9 100644 --- a/minipath/src/Config.cpp +++ b/minipath/src/Config.cpp @@ -648,32 +648,34 @@ void InitDefaultSettings() //============================================================================= // -// CreateIniFile() +// CreateIniFileEx() // // -int CreateIniFile() +static int CreateIniFileEx(LPCWSTR lpszPath) { int result = 0; - if (g_wchIniFile[0] != L'\0') { - WCHAR* pwchTail = StrRChrW(g_wchIniFile, NULL, L'\\'); + if (lpszPath[0] != L'\0') { + + WCHAR tchDir[MAX_PATH]; + lstrcpy(tchDir, lpszPath); + WCHAR* pwchTail = StrRChrW(tchDir, NULL, L'\\'); if (pwchTail) { *pwchTail = 0; - SHCreateDirectoryEx(NULL, g_wchIniFile, NULL); - *pwchTail = L'\\'; + SHCreateDirectoryEx(NULL, tchDir, NULL); } DWORD dwFileSize = 0UL; - if (!PathIsExistingFile(g_wchIniFile)) { - HANDLE hFile = CreateFile(g_wchIniFile, + if (!PathIsExistingFile(lpszPath)) { + HANDLE hFile = CreateFile(lpszPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); } } else { - HANDLE hFile = CreateFile(g_wchIniFile, + HANDLE hFile = CreateFile(lpszPath, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile == INVALID_HANDLE_VALUE) { @@ -685,7 +687,7 @@ int CreateIniFile() } if ((dwFileSize == 0) && (dwFileSize != INVALID_FILE_SIZE)) { - result = IniFileSetString(g_wchIniFile, L"minipath", NULL, NULL); + result = IniFileSetString(lpszPath, L"minipath", NULL, NULL); } else { result = true; } @@ -694,6 +696,17 @@ int CreateIniFile() } return result; } + + +//============================================================================= +// +// CreateIniFile() +// +// +int CreateIniFile() +{ + return CreateIniFileEx(g_wchIniFile); +} //============================================================================= @@ -764,11 +777,12 @@ int CheckIniFileRedirect(LPCWSTR lpszAppName, LPCWSTR lpszKeyName, LPWSTR lpszFi if (PathIsRelative(tchFileExpanded)) { lstrcpy(lpszFile, lpszModule); lstrcpy(PathFindFileName(lpszFile), tchFileExpanded); - return(1); } else { lstrcpy(lpszFile, tchFileExpanded); - return(1); } + // Redirect target doesn't exist — try to create it + CreateIniFileEx(lpszFile); + return(1); } } return(0); diff --git a/np3portableapp/Notepad3Portable/App/Notepad3/x64/np3/Notepad3.ini b/np3portableapp/Notepad3Portable/App/Notepad3/x64/np3/Notepad3.ini index 68e5f5766..84db915cf 100644 --- a/np3portableapp/Notepad3Portable/App/Notepad3/x64/np3/Notepad3.ini +++ b/np3portableapp/Notepad3Portable/App/Notepad3/x64/np3/Notepad3.ini @@ -1,2 +1,4 @@ [Notepad3] Notepad3.ini=%NOTEPAD3_PORTABLE_SETTINGS%\Notepad3.ini +[Settings2] +DefaultDirectory=%CSIDL:MYDOCUMENTS% diff --git a/np3portableapp/Notepad3Portable/App/Notepad3/x86/np3/Notepad3.ini b/np3portableapp/Notepad3Portable/App/Notepad3/x86/np3/Notepad3.ini index 68e5f5766..84db915cf 100644 --- a/np3portableapp/Notepad3Portable/App/Notepad3/x86/np3/Notepad3.ini +++ b/np3portableapp/Notepad3Portable/App/Notepad3/x86/np3/Notepad3.ini @@ -1,2 +1,4 @@ [Notepad3] Notepad3.ini=%NOTEPAD3_PORTABLE_SETTINGS%\Notepad3.ini +[Settings2] +DefaultDirectory=%CSIDL:MYDOCUMENTS% diff --git a/src/Config/Config.cpp b/src/Config/Config.cpp index 4713c610b..7e2e7f422 100644 --- a/src/Config/Config.cpp +++ b/src/Config/Config.cpp @@ -990,10 +990,21 @@ static bool _CheckAndSetIniFile(HPATHL hpth_in_out) // ============================================================================ -static bool _HandleIniFileRedirect(LPCWSTR lpszSecName, LPCWSTR lpszKeyName, HPATHL hpth_in_out) +static bool _HandleIniFileRedirect(LPCWSTR lpszSecName, LPCWSTR lpszKeyName, HPATHL hpth_in_out, int maxDepth) { bool result = false; - if (Path_IsExistingFile(hpth_in_out)) { + for (int depth = 0; depth < maxDepth; ++depth) { + if (!Path_IsExistingFile(hpth_in_out)) { + break; + } + + // pick up DefaultDirectory from each redirecting INI (later files override earlier ones) + WCHAR defDir[PATHLONG_MAX_CCH] = { L'\0' }; + if (IniFileGetString(hpth_in_out, Constants.Settings2_Section, L"DefaultDirectory", L"", defDir, COUNTOF(defDir))) { + Path_Reset(Settings2.DefaultDirectory, defDir); + Path_ExpandEnvStrings(Settings2.DefaultDirectory); + } + HPATHL hredirect = Path_Allocate(NULL); LPWSTR const buf = Path_WriteAccessBuf(hredirect, PATHLONG_MAX_CCH); if (IniFileGetString(hpth_in_out, lpszSecName, lpszKeyName, L"", buf, PATHLONG_MAX_CCH)) { @@ -1020,6 +1031,10 @@ static bool _HandleIniFileRedirect(LPCWSTR lpszSecName, LPCWSTR lpszKeyName, HPA } result = true; } + else { + Path_Release(hredirect); + break; // no redirect entry found, stop chaining + } Path_Release(hredirect); } return result; @@ -1055,11 +1070,8 @@ extern "C" bool FindIniFile() } if (bFound) { - // allow two redirections: administrator -> user -> custom - // 1st: - if (_HandleIniFileRedirect(_W(SAPPNAME), _W(SAPPNAME) L".ini", Paths.IniFile)) { - // 2nd: - _HandleIniFileRedirect(_W(SAPPNAME), _W(SAPPNAME) L".ini", Paths.IniFile); + // allow up to two redirections: administrator -> user -> custom + if (_HandleIniFileRedirect(_W(SAPPNAME), _W(SAPPNAME) L".ini", Paths.IniFile, 2)) { bFound = _CheckAndSetIniFile(Paths.IniFile); } @@ -1258,11 +1270,15 @@ void LoadSettings() StrTrim(Settings2.DefaultExtension, L" \t."); IniSectionGetStringNoQuotes(IniSecSettings2, L"DefaultDirectory", L"", pPathBuffer, PATHLONG_MAX_CCH); - Path_Reset(Settings2.DefaultDirectory, pPathBuffer); - Path_ExpandEnvStrings(Settings2.DefaultDirectory); + if (StrIsNotEmpty(pPathBuffer)) { + Path_Reset(Settings2.DefaultDirectory, pPathBuffer); + Path_ExpandEnvStrings(Settings2.DefaultDirectory); + } IniSectionGetStringNoQuotes(IniSecSettings2, L"FileDlgFilters", L"", pPathBuffer, XHUGE_BUFFER); - StrgReset(Settings2.FileDlgFilters, pPathBuffer); + if (StrIsNotEmpty(pPathBuffer)) { + StrgReset(Settings2.FileDlgFilters, pPathBuffer); + } // handle deprecated (typo) key 'FileCheckInverval' constexpr const LONG64 NOTSETFCI = -111LL; diff --git a/src/PathLib.c b/src/PathLib.c index 1491838b4..4b1a2200f 100644 --- a/src/PathLib.c +++ b/src/PathLib.c @@ -182,6 +182,8 @@ LPCWSTR const PATHPARENT_PREFIX = L"..\\"; LPCWSTR const PATHDSPL_INFIX = L" ... "; LPCWSTR const PATH_CSIDL_MYDOCUMENTS = L"%CSIDL:MYDOCUMENTS%"; +LPCWSTR const PATH_CSIDL_DESKTOP = L"%CSIDL:DESKTOP%"; +LPCWSTR const PATH_CSIDL_FAVORITES = L"%CSIDL:FAVORITES%"; // TODO: ... @@ -1089,7 +1091,39 @@ void PTHAPI Path_ExpandEnvStrings(HPATHL hpth_in_out) HSTRINGW hstr_io = ToHStrgW(hpth_in_out); if (!hstr_io) return; - + + // resolve %CSIDL:MYDOCUMENTS% before standard env-var expansion + if (StrgFind(hstr_io, PATH_CSIDL_MYDOCUMENTS, 0) >= 0) { + HPATHL hfld_pth = Path_Allocate(NULL); + if (!Path_GetKnownFolder(&FOLDERID_Documents, hfld_pth)) { + if (!Path_GetCurrentDirectory(hfld_pth)) { + Path_GetAppDirectory(hfld_pth); + } + } + StrgReplace(hstr_io, PATH_CSIDL_MYDOCUMENTS, PathGet(hfld_pth)); + Path_Release(hfld_pth); + } + + // resolve %CSIDL:DESKTOP% + if (StrgFind(hstr_io, PATH_CSIDL_DESKTOP, 0) >= 0) { + HPATHL hfld_pth = Path_Allocate(NULL); + if (!Path_GetKnownFolder(&FOLDERID_Desktop, hfld_pth)) { + Path_GetAppDirectory(hfld_pth); + } + StrgReplace(hstr_io, PATH_CSIDL_DESKTOP, PathGet(hfld_pth)); + Path_Release(hfld_pth); + } + + // resolve %CSIDL:FAVORITES% + if (StrgFind(hstr_io, PATH_CSIDL_FAVORITES, 0) >= 0) { + HPATHL hfld_pth = Path_Allocate(NULL); + if (!Path_GetKnownFolder(&FOLDERID_Favorites, hfld_pth)) { + Path_GetAppDirectory(hfld_pth); + } + StrgReplace(hstr_io, PATH_CSIDL_FAVORITES, PathGet(hfld_pth)); + Path_Release(hfld_pth); + } + ExpandEnvironmentStrgs(hstr_io, true); } // ---------------------------------------------------------------------------- @@ -1821,6 +1855,25 @@ bool PTHAPI Path_CanonicalizeEx(HPATHL hpth_in_out, const HPATHL hdir_rel_base) StrgReplace(hstr_io, PATH_CSIDL_MYDOCUMENTS, PathGet(hfld_pth)); Path_Release(hfld_pth); } + + if (StrgFind(hstr_io, PATH_CSIDL_DESKTOP, 0) == 0) { + HPATHL hfld_pth = Path_Allocate(NULL); + if (!Path_GetKnownFolder(&FOLDERID_Desktop, hfld_pth)) { + Path_GetAppDirectory(hfld_pth); + } + StrgReplace(hstr_io, PATH_CSIDL_DESKTOP, PathGet(hfld_pth)); + Path_Release(hfld_pth); + } + + if (StrgFind(hstr_io, PATH_CSIDL_FAVORITES, 0) == 0) { + HPATHL hfld_pth = Path_Allocate(NULL); + if (!Path_GetKnownFolder(&FOLDERID_Favorites, hfld_pth)) { + Path_GetAppDirectory(hfld_pth); + } + StrgReplace(hstr_io, PATH_CSIDL_FAVORITES, PathGet(hfld_pth)); + Path_Release(hfld_pth); + } + ExpandEnvironmentStrgs(hstr_io, true); bool res = false; @@ -2059,6 +2112,16 @@ void PTHAPI Path_RelativeToApp(HPATHL hpth_in_out, bool bSrcIsFile, bool bUnexpa } } + HPATHL const hdesktop_pth = Path_Allocate(NULL); + if (!Path_GetKnownFolder(&FOLDERID_Desktop, hdesktop_pth)) { + Path_GetAppDirectory(hdesktop_pth); + } + + HPATHL const hfavorites_pth = Path_Allocate(NULL); + if (!Path_GetKnownFolder(&FOLDERID_Favorites, hfavorites_pth)) { + Path_GetAppDirectory(hfavorites_pth); + } + HPATHL const hprgs_pth = Path_Allocate(NULL); #ifdef _WIN64 if (!Path_GetKnownFolder(&FOLDERID_ProgramFiles, hprgs_pth)) { @@ -2075,14 +2138,28 @@ void PTHAPI Path_RelativeToApp(HPATHL hpth_in_out, bool bSrcIsFile, bool bUnexpa bool const bPathIsRelative = _Path_IsRelative(hpth_in_out); bool const bAppPathIsUsrDoc = Path_IsPrefix(husrdoc_pth, happdir_pth); bool const bPathIsPrefixUsrDoc = Path_IsPrefix(husrdoc_pth, hpth_in_out); + bool const bAppPathIsDesktop = Path_IsPrefix(hdesktop_pth, happdir_pth); + bool const bPathIsPrefixDesktop = Path_IsPrefix(hdesktop_pth, hpth_in_out); + bool const bAppPathIsFavorites = Path_IsPrefix(hfavorites_pth, happdir_pth); + bool const bPathIsPrefixFavorites = Path_IsPrefix(hfavorites_pth, hpth_in_out); DWORD dwAttrTo = (bSrcIsFile) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_DIRECTORY; - + HPATHL htmp_pth = Path_Allocate(NULL); if (bUnexpandMyDocs && !bPathIsRelative && !bAppPathIsUsrDoc && bPathIsPrefixUsrDoc && _Path_RelativePathTo(htmp_pth, husrdoc_pth, FILE_ATTRIBUTE_DIRECTORY, hpth_in_out, dwAttrTo)) { Path_Reset(hpth_in_out, PATH_CSIDL_MYDOCUMENTS); Path_Append(hpth_in_out, Path_Get(htmp_pth)); } + else if (bUnexpandMyDocs && !bPathIsRelative && !bAppPathIsDesktop && bPathIsPrefixDesktop + && _Path_RelativePathTo(htmp_pth, hdesktop_pth, FILE_ATTRIBUTE_DIRECTORY, hpth_in_out, dwAttrTo)) { + Path_Reset(hpth_in_out, PATH_CSIDL_DESKTOP); + Path_Append(hpth_in_out, Path_Get(htmp_pth)); + } + else if (bUnexpandMyDocs && !bPathIsRelative && !bAppPathIsFavorites && bPathIsPrefixFavorites + && _Path_RelativePathTo(htmp_pth, hfavorites_pth, FILE_ATTRIBUTE_DIRECTORY, hpth_in_out, dwAttrTo)) { + Path_Reset(hpth_in_out, PATH_CSIDL_FAVORITES); + Path_Append(hpth_in_out, Path_Get(htmp_pth)); + } else if (!bPathIsRelative && !Path_CommonPrefix(happdir_pth, hprgs_pth, NULL)) { if (_Path_RelativePathTo(htmp_pth, happdir_pth, FILE_ATTRIBUTE_DIRECTORY, hpth_in_out, dwAttrTo)) { Path_Swap(hpth_in_out, htmp_pth); @@ -2091,6 +2168,8 @@ void PTHAPI Path_RelativeToApp(HPATHL hpth_in_out, bool bSrcIsFile, bool bUnexpa Path_Release(htmp_pth); Path_Release(hprgs_pth); + Path_Release(hfavorites_pth); + Path_Release(hdesktop_pth); Path_Release(husrdoc_pth); Path_Release(happdir_pth); @@ -2225,6 +2304,24 @@ void PTHAPI Path_AbsoluteFromApp(HPATHL hpth_in_out, bool bExpandEnv) Path_Release(hfld_pth); } + if (StrgFind(hstr_in_out, PATH_CSIDL_DESKTOP, 0) == 0) { + HPATHL hfld_pth = Path_Allocate(NULL); + if (!Path_GetKnownFolder(&FOLDERID_Desktop, hfld_pth)) { + Path_GetAppDirectory(hfld_pth); + } + StrgReplace(htmp_str, PATH_CSIDL_DESKTOP, PathGet(hfld_pth)); + Path_Release(hfld_pth); + } + + if (StrgFind(hstr_in_out, PATH_CSIDL_FAVORITES, 0) == 0) { + HPATHL hfld_pth = Path_Allocate(NULL); + if (!Path_GetKnownFolder(&FOLDERID_Favorites, hfld_pth)) { + Path_GetAppDirectory(hfld_pth); + } + StrgReplace(htmp_str, PATH_CSIDL_FAVORITES, PathGet(hfld_pth)); + Path_Release(hfld_pth); + } + if (bExpandEnv) { Path_ExpandEnvStrings(htmp_pth); }