From 75fdb71a872579d5d1a743d53e9309acab8a1ad5 Mon Sep 17 00:00:00 2001 From: combwizard <18099578+combwizard@users.noreply.github.com> Date: Wed, 4 Nov 2026 11:53:06 +0100 Subject: [PATCH 1/2] Docs: a documentation site at /docs, built with MkDocs from docs/ MkDocs Material, set up like ZOrbit's: pages in docs/, theme overrides in docs-theme/ and docs/stylesheets/extra.css, published with the landing page by pages.yml. Pull requests that touch the docs run a strict build. Differs from ZOrbit in following the system's light or dark mode, like the landing page, and in site_dir: MkDocs' default output is site/, which it empties first, and that is the landing page's source here. Screenshots move to docs/images/. The manual test checklist moves from the README to docs/building.md. CLAUDE.md keeps the docs in step with the code. --- .github/workflows/pages.yml | 59 ++- .gitignore | 1 + CLAUDE.md | 46 ++ README.md | 47 +- docs-theme/partials/footer.html | 42 ++ docs-theme/partials/header.html | 32 ++ docs-theme/partials/javascripts/palette.html | 15 + docs-theme/partials/logo.html | 2 + docs/apps.md | 157 +++++++ docs/assets/icon.png | Bin 0 -> 20032 bytes docs/building.md | 110 +++++ docs/config.md | 251 +++++++++++ docs/dialogs.md | 100 +++++ docs/files.md | 123 +++++ docs/getting-started.md | 80 ++++ docs/how-it-works.md | 233 ++++++++++ docs/{ => images}/explorer-dark.gif | Bin docs/{ => images}/explorer-light.gif | Bin docs/{ => images}/explorer-still-dark.png | Bin docs/{ => images}/explorer-still-light.png | Bin docs/{ => images}/menu-dark.png | Bin docs/{ => images}/menu-light.png | Bin docs/{ => images}/save-dialog-dark.gif | Bin docs/{ => images}/save-dialog-light.gif | Bin docs/{ => images}/save-dialog-still-dark.png | Bin docs/{ => images}/save-dialog-still-light.png | Bin docs/{ => images}/settings-dark.png | Bin docs/{ => images}/settings-light.png | Bin docs/index.md | 28 ++ docs/install.md | 83 ++++ docs/javascripts/lightbox.js | 133 ++++++ docs/menu.md | 188 ++++++++ docs/settings.md | 254 +++++++++++ docs/stylesheets/extra.css | 422 ++++++++++++++++++ docs/troubleshooting.md | 173 +++++++ mkdocs.yml | 83 ++++ requirements-docs.txt | 1 + site/index.html | 34 +- site/preview.ps1 | 8 +- site/stage.ps1 | 4 +- tools/screenshots/capture.ps1 | 4 +- 41 files changed, 2659 insertions(+), 54 deletions(-) create mode 100644 CLAUDE.md create mode 100644 docs-theme/partials/footer.html create mode 100644 docs-theme/partials/header.html create mode 100644 docs-theme/partials/javascripts/palette.html create mode 100644 docs-theme/partials/logo.html create mode 100644 docs/apps.md create mode 100644 docs/assets/icon.png create mode 100644 docs/building.md create mode 100644 docs/config.md create mode 100644 docs/dialogs.md create mode 100644 docs/files.md create mode 100644 docs/getting-started.md create mode 100644 docs/how-it-works.md rename docs/{ => images}/explorer-dark.gif (100%) rename docs/{ => images}/explorer-light.gif (100%) rename docs/{ => images}/explorer-still-dark.png (100%) rename docs/{ => images}/explorer-still-light.png (100%) rename docs/{ => images}/menu-dark.png (100%) rename docs/{ => images}/menu-light.png (100%) rename docs/{ => images}/save-dialog-dark.gif (100%) rename docs/{ => images}/save-dialog-light.gif (100%) rename docs/{ => images}/save-dialog-still-dark.png (100%) rename docs/{ => images}/save-dialog-still-light.png (100%) rename docs/{ => images}/settings-dark.png (100%) rename docs/{ => images}/settings-light.png (100%) create mode 100644 docs/index.md create mode 100644 docs/install.md create mode 100644 docs/javascripts/lightbox.js create mode 100644 docs/menu.md create mode 100644 docs/settings.md create mode 100644 docs/stylesheets/extra.css create mode 100644 docs/troubleshooting.md create mode 100644 mkdocs.yml create mode 100644 requirements-docs.txt diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 5388ed9..40b4657 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,35 +1,84 @@ name: pages -# Publishes combwizard.github.io/folderite from site/ plus the screenshots in docs/. +# Publishes combwizard.github.io/Folderite: the landing page from site/ at the root and the +# documentation (docs/ + mkdocs.yml) under /docs/. on: push: branches: [main] - paths: ['site/**', 'docs/**', '.github/workflows/pages.yml'] + paths: + - "site/**" + - "docs/**" + - "docs-theme/**" + - "mkdocs.yml" + - "requirements-docs.txt" + - ".github/workflows/pages.yml" + # Pull requests only build the docs (strict: broken links and anchors fail), they don't deploy. + pull_request: + paths: + - "docs/**" + - "docs-theme/**" + - "mkdocs.yml" + - "requirements-docs.txt" workflow_dispatch: permissions: contents: read - pages: write - id-token: write concurrency: group: pages cancel-in-progress: false jobs: + check: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-python@v5 + with: + python-version: "3.14" + cache: pip + cache-dependency-path: requirements-docs.txt + + - name: Build docs + env: + NO_MKDOCS_2_WARNING: "1" + run: | + pip install -r requirements-docs.txt + mkdocs build --strict + deploy: + if: github.event_name != 'pull_request' runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - uses: actions/checkout@v7 - - name: Stage site + - uses: actions/setup-python@v5 + with: + python-version: "3.14" + cache: pip + cache-dependency-path: requirements-docs.txt + + - name: Stage landing page shell: pwsh run: ./site/stage.ps1 -Out _site + - name: Build docs + env: + NO_MKDOCS_2_WARNING: "1" + run: | + pip install -r requirements-docs.txt + mkdocs build --strict -d "$GITHUB_WORKSPACE/_site/docs" + - uses: actions/configure-pages@v5 - uses: actions/upload-pages-artifact@v3 diff --git a/.gitignore b/.gitignore index 97f483b..5a74955 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ TestResults/ # Local *.log site/.preview/ +_site/ # Screenshot harness output tools/screenshots/.work/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7c63ef2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,46 @@ +# Folderite + +A middle-click folder menu for Windows. [README.md](README.md) says what it does, and [docs/building.md](docs/building.md) +how to build and test it. + +## Keep the docs in step with the code + +The user docs are the Markdown pages in `docs/`. Every push to `main` that touches them publishes them to +, so they describe `main`, not the last release. + +- **A change a user can see or do updates its docs page in the same commit.** That covers settings, menu items, config + keys, defaults, supported apps, file names and locations, log lines, messages, requirements and install steps. A new + feature is documented where a user would look for it; a removed one is taken out of every page that mentions it. +- **Check `README.md` and the landing page (`site/index.html`)** when the change touches install, requirements, first + steps or the feature list. +- **Pictures:** `tools\screenshots\capture.ps1` makes everything in `docs/images/`, in a light and a dark version. When a + change alters something in a picture, retake it in both Windows themes and look it over before committing. Pages + show both versions, one after the other, with `#only-light` and `#only-dark` on the file names. +- **Build the docs** after editing them: `mkdocs build --strict` (`pip install -r requirements-docs.txt` first). It + fails on a broken link or anchor. Link between pages with relative `.md` links so they work on GitHub too. +- **Keep `site_dir` in `mkdocs.yml`.** MkDocs' default output folder is `site/`, which it empties first, and `site/` is + the landing page's source here. Don't pass `-d site` either. + +Where each part is documented: + +| Code | Page in `docs/` | +|---|---| +| `Menu/` (sections, order, accelerators, subfolders, drives) | `menu.md`, `config.md` | +| `Tray/` (the tray icon and its menu, Start with Windows) | `menu.md`, `install.md` | +| `Hooks/` (middle-click, keyboard shortcut) | `menu.md`, `troubleshooting.md`, `how-it-works.md` | +| `History/` (recent folders, frecency) | `menu.md`, `how-it-works.md` | +| `Settings/` (the Settings window) | `settings.md` | +| `Dialogs/`, `Targets/FileDialogAdapter.cs` (Open and Save dialogs, start folders) | `dialogs.md`, `settings.md` | +| `Targets/` (supported apps) | `apps.md`, `how-it-works.md` | +| `Config/AppConfig.cs` (config keys and defaults) | `config.md`, `settings.md` | +| `Config/ConfigLocation.cs`, `ConfigRelocator.cs`, `Settings/ConfigMove.cs`, `Log.cs` (where files live) | `files.md` | +| `Interop/` (Settings and tray app talking) | `how-it-works.md` | +| A new way for something to fail, or a new log line worth reading | `troubleshooting.md` | +| `tests/`, `tools/`, `.github/workflows/`, `Folderite.csproj` | `building.md`, and `install.md` for requirements | + +Write like the existing pages: plain, short, concrete sentences, American spelling (favorites, color), UI labels and +keys in **bold**, no em dashes, no marketing words. + +The docs theme is in `docs-theme/` and `docs/stylesheets/extra.css`. It copies the landing page's look and tokens from +`site/styles.css`, so a change to the landing page's look may need the same change there. Pages follow the system's +light or dark mode, with no switch, like the landing page. diff --git a/README.md b/README.md index 85d2bcd..c7fa9a2 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ # Folderite -[![CI](https://github.com/combwizard/folderite/actions/workflows/ci.yml/badge.svg)](https://github.com/combwizard/folderite/actions/workflows/ci.yml) -[![Latest release](https://img.shields.io/github/v/release/combwizard/folderite)](https://github.com/combwizard/folderite/releases/latest) +[![CI](https://github.com/combwizard/Folderite/actions/workflows/ci.yml/badge.svg)](https://github.com/combwizard/Folderite/actions/workflows/ci.yml) +[![Latest release](https://img.shields.io/github/v/release/combwizard/Folderite)](https://github.com/combwizard/Folderite/releases/latest) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -[![Website](https://img.shields.io/badge/website-combwizard.github.io%2Ffolderite-f2b632)](https://combwizard.github.io/folderite/) +[![Website](https://img.shields.io/badge/website-combwizard.github.io%2FFolderite-f2b632)](https://combwizard.github.io/Folderite/) +[![Docs](https://img.shields.io/badge/docs-combwizard.github.io%2FFolderite%2Fdocs-3d9bff)](https://combwizard.github.io/Folderite/docs/) Middle-click in a file manager or Open/Save dialog to pop up a folder menu: favorites, folders open in other windows, recent folders (frecency-ranked), and the folder on the clipboard. Picking one sends *that* window there. - - The Folderite menu over an Explorer window: the current folder, favorites, open windows, recent folders and the clipboard folder + + The Folderite menu over an Explorer window: the current folder, favorites, open windows, recent folders and the clipboard folder Supported: Windows Explorer (incl. Win11 tabs), common file dialogs, Directory Opus, Total Commander, XYplorer, Blender's file browser window, and the desktop (opens a new Explorer window). @@ -17,7 +18,7 @@ Supported: Windows Explorer (incl. Win11 tabs), common file dialogs, Directory O ## Install 1. Install the [.NET 10 Desktop Runtime (x64)](https://dotnet.microsoft.com/download/dotnet/10.0) if you don't have it. -2. Download `Folderite--win-x64.zip` from the [latest release](https://github.com/combwizard/folderite/releases/latest), unzip it to a folder of your choice, and run `Folderite.exe`. +2. Download `Folderite--win-x64.zip` from the [latest release](https://github.com/combwizard/Folderite/releases/latest), unzip it to a folder of your choice, and run `Folderite.exe`. To upgrade, quit Folderite from the tray and unzip the new release over the old folder. Your settings are kept. @@ -33,6 +34,10 @@ dotnet test src\Folderite\bin\Debug\net10.0-windows\win-x64\Folderite.exe ``` +## Documentation + +The full guide is at [combwizard.github.io/Folderite/docs](https://combwizard.github.io/Folderite/docs/), built from [`docs/`](docs/). + ## Usage Runs in the tray. Right-click the tray icon for Settings, Pause, Start with Windows, Edit config, and Exit. Left-click opens the folder menu; picking a folder there opens a new Explorer window. @@ -42,15 +47,15 @@ You can also open the menu with a keyboard shortcut: set one under **Settings Middle-click inside Explorer and pick a favorite or a recent folder: - - Middle-clicking in Explorer, picking Work > Specs, then a recent folder + + Middle-clicking in Explorer, picking Work > Specs, then a recent folder In an Open or Save dialog, the file name you typed is kept: - - Middle-clicking in a Save As dialog and picking Work > Reports; the typed file name stays + + Middle-clicking in a Save As dialog and picking Work > Reports; the typed file name stays ## Settings @@ -58,8 +63,8 @@ In an Open or Save dialog, the file name you typed is kept: **Settings…** (tray menu, or the bottom of the folder menu) opens an editor for everything below: favorites (drag to reorder or nest, drop folders from Explorer), actions, menu sections, exclusions, start folders for Open/Save dialogs, your folder history, Start with Windows / Pause, and the keyboard shortcut. Changes save as you make them and the running app picks them up immediately. **Preview menu** shows the real menu with the current settings. - - The Settings window on the Favorites page + + The Settings window on the Favorites page Settings rewrites `config.json` when it saves, so `//` comments in it are not kept. If the file has an error, Settings stops saving until it is fixed. @@ -117,25 +122,11 @@ Recent folders are ranked by how often and how recently you used them, with a bo ## Manual test checklist -- [ ] Explorer: menu shows; favorite navigates; with several tabs, the *active* tab navigates -- [ ] Explorer (Win11): middle-click on a tab still closes it (tab row is exempt) -- [ ] Open/Save dialog (Notepad, Office, browser): navigates; typed filename is kept -- [ ] Folder picker dialog: navigates instead of accepting the folder -- [ ] Directory Opus dual pane: clicked side navigates -- [ ] Total Commander: left/right panel, non-ASCII path *(untested so far)* -- [ ] XYplorer: `::goto` with spaces *(untested so far)* -- [ ] Blender 5.x File View (Open, Import with options panel, folder picker): navigates from a click on the bookmarks and on the file list; clipboard (text and image) comes back; typed filename is kept -- [ ] Middle-click elsewhere (browser tab, editor) still works normally -- [ ] Elevated Explorer/dialog: fails gracefully unless Folderite runs elevated -- [ ] Keyboard shortcut: opens the menu for Explorer (active tab), a file dialog, Directory Opus (focused side) and the desktop; elsewhere picking a folder opens a new Explorer window; does nothing while paused; a shortcut that's taken shows a warning in Settings -- [ ] Favorite with subfolders: submenu lists subfolders (by name / newest first); the folder itself is the bold top item; a disconnected network folder doesn't freeze the menu -- [ ] Recent folders: folders used in the current app rank higher there -- [ ] Dialog start folder: off by default; when on, a new Open/Save dialog moves to the app's last folder and keeps the typed file name; a per-app folder wins; folder pickers work -- [ ] Drives submenu (when enabled): lists drives with free space; a disconnected network drive doesn't hang the menu +The list of what to try by hand before a release is in [Build from source](docs/building.md#manual-test-checklist). ## Screenshots -Every screenshot and animation in `docs/` exists in a light and a dark version (`-light.*`, `-dark.*`); the README and the website pick one by the viewer's theme. To retake them, switch Windows to light mode and run `pwsh tools\screenshots\capture.ps1`, then switch to dark mode and run it again. It drives a demo setup with made-up folders on a temporary `X:` drive and takes over the mouse for about a minute. +Every screenshot and animation in `docs/images/` exists in a light and a dark version (`-light.*`, `-dark.*`); the README and the website pick one by the viewer's theme. To retake them, switch Windows to light mode and run `pwsh tools\screenshots\capture.ps1`, then switch to dark mode and run it again. It drives a demo setup with made-up folders on a temporary `X:` drive and takes over the mouse for about a minute. ## Releasing diff --git a/docs-theme/partials/footer.html b/docs-theme/partials/footer.html new file mode 100644 index 0000000..948b30b --- /dev/null +++ b/docs-theme/partials/footer.html @@ -0,0 +1,42 @@ +{#- Material's previous/next links, then the landing page's footer. -#} + diff --git a/docs-theme/partials/header.html b/docs-theme/partials/header.html new file mode 100644 index 0000000..6254b0f --- /dev/null +++ b/docs-theme/partials/header.html @@ -0,0 +1,32 @@ +{#- Material's header, reshaped into the landing page's floating bar: icon and wordmark + (back to the landing page), a Docs link, search, and the GitHub and Download buttons. + The first include replaces Material's own palette script (system light or dark, no toggle). -#} +{% include "partials/javascripts/palette.html" %} +
+ +
diff --git a/docs-theme/partials/javascripts/palette.html b/docs-theme/partials/javascripts/palette.html new file mode 100644 index 0000000..2024a64 --- /dev/null +++ b/docs-theme/partials/javascripts/palette.html @@ -0,0 +1,15 @@ +{#- Replaces Material's palette script. Like the landing page there is no theme switch: the page follows the + system's light or dark mode, also when it changes while the page is open. Material's only-light and only-dark + pictures hang off this attribute. Included from partials/header.html, so it runs before the page paints. -#} + diff --git a/docs-theme/partials/logo.html b/docs-theme/partials/logo.html new file mode 100644 index 0000000..88e6f4c --- /dev/null +++ b/docs-theme/partials/logo.html @@ -0,0 +1,2 @@ +{#- The Folderite icon, same file as the landing page's favicon (docs/assets/icon.png, set as theme.favicon). -#} + diff --git a/docs/apps.md b/docs/apps.md new file mode 100644 index 0000000..00f7c38 --- /dev/null +++ b/docs/apps.md @@ -0,0 +1,157 @@ +# Supported apps + +Folderite opens its menu when you middle-click in one of the windows below, or press your [keyboard shortcut](menu.md) while one is in front. Picking a folder sends that window there. Anywhere else, middle-click does what it always did. + +In every supported window, the first item of the menu is the **current folder**: its name, with the shell icon, and a submenu arrow. Open it for **Copy path**, your custom actions and **Add to favorites**. If that item is missing, Folderite couldn't read the window's folder. Picking a folder still works in most of those cases. The sections below say when. + +| App | Reads the current folder | Sends the window to a folder | +| --- | --- | --- | +| [Windows Explorer](#windows-explorer) | Yes, from the active tab | Yes | +| [Open and Save dialogs](#open-and-save-dialogs) | Yes | Yes | +| [Directory Opus](#directory-opus) | Yes, from the pane you clicked | Yes | +| [Total Commander](#total-commander) | Yes, from the panel you clicked | Yes (not yet tested on every setup) | +| [XYplorer](#xyplorer) | Yes, from the title bar | Yes (not yet tested on every setup) | +| [Blender](#blender) | No | Yes | +| [The desktop](#the-desktop) | Always the Desktop folder | Opens a new Explorer window | + +## Windows Explorer + +Works in Explorer windows, including Windows 11 windows with tabs. + +![Middle-clicking in Explorer, picking Work, then Specs, then a recent folder](images/explorer-light.gif#only-light) +![Middle-clicking in Explorer, picking Work, then Specs, then a recent folder](images/explorer-dark.gif#only-dark) + +**What works.** Middle-click on the file list or any other part of the window except the tab row. The menu opens, and the folder you pick replaces what the window shows. With several tabs open, the active tab navigates. The **Open windows** section lists the folder of each Explorer tab. It shows each folder once, and at most 10 folders in all, counting other apps. + +**How you can tell.** The top item of the menu is the folder of the active tab. In the picture, it is `aurora-web`. + +**Limits.** + +- Middle-click on a tab, or anywhere in the title bar row beside the tabs, keeps its normal meaning. A middle-click on a tab still closes it. An Explorer window without tabs has no such exemption. +- Virtual folders have no file system path: **This PC**, **Home**, and search results. In those, the menu has no current folder item. You can still pick a folder to leave them. +- If Explorer can't be driven, a balloon from the tray icon says "Couldn't open ... in Explorer." + +**Keyboard shortcut.** It works in Explorer and uses the active tab. It opens the menu at the mouse if the mouse is over the window, and at the center of the window if not. The tab-row exemption doesn't apply, because no click is involved. + +## Open and Save dialogs + +Works in the standard Windows Open, Save As and folder-picker dialogs, in any program that uses them (Notepad, Office, browsers). The folder you pick replaces the dialog's folder, and the file name you already typed stays. Folder pickers navigate instead of accepting the folder. + +![Middle-clicking in a Save As dialog and picking a folder; the typed file name stays](images/save-dialog-light.gif#only-light) +![Middle-clicking in a Save As dialog and picking a folder; the typed file name stays](images/save-dialog-dark.gif#only-dark) + +**How you can tell.** The top item is the folder shown in the dialog's address bar. + +**Limits.** A program that draws its own file dialog is not recognized. If the dialog's address bar can't be read, there is no current folder item. + +You can also have new dialogs start in a folder you choose. That is covered in [Open and Save dialogs](dialogs.md). + +**Keyboard shortcut.** It works with the dialog in front, the same as a click. + +## Directory Opus + +Works in Directory Opus listers, in either pane and in any tab. Folderite talks to Opus through `dopusrt.exe`, the command-line program that comes with it. It looks for it next to the running `dopus.exe` first, then in `C:\Program Files\GPSoftware\Directory Opus\`. + +**What works.** Middle-click anywhere in the lister. The menu shows the folder of the tab you clicked, and the folder you pick goes into that pane. If you click the pane that isn't the active one, Folderite makes it the active one first, then sends the folder there. The **Open windows** section lists the tabs of all listers. + +**How you can tell.** The top item is the folder of the tab under the cursor. If the click isn't on a tab, it is the folder of the source pane. + +**Limits.** + +- Each time the menu opens, Folderite starts `dopusrt.exe` and waits up to two seconds for it. If `dopusrt.exe` can't be found or doesn't answer, the menu has no current folder and navigation fails with a tray balloon. +- Folderite brings the lister you clicked to the front before sending the folder, because Opus sends it to the lister that was active most recently. + +**Keyboard shortcut.** It works with Opus in front. Folderite uses the control that has the keyboard focus to pick the pane, so it is the focused side that navigates. If the focus isn't on a tab, it uses the source pane. + +## Total Commander + +Works in Total Commander (the `TTOTAL_CMD` window). It needs Total Commander 9 or later. + +**What works.** Middle-click anywhere in the window. The menu shows the folder of the panel you clicked, and the folder you pick goes into that panel. The **Open windows** section lists both panels of every Total Commander window. + +**How you can tell.** The top item is the folder of the clicked panel. If Folderite can't tell which panel the click was in, it shows the active one and sends the folder to the source panel. + +**Not tested yet.** Two cases haven't been tried on a real Total Commander yet, so treat them as unconfirmed: + +- Which panel gets the folder when you click in the left or the right one. +- Paths with non-ASCII characters. The folder is sent as UTF-8, which should work, but nobody has checked it. + +If either goes wrong for you, [tell us](https://github.com/combwizard/Folderite/issues). + +**Limits.** A panel that doesn't show a real folder (a plugin or an FTP connection, for example) gives no current folder. + +**Keyboard shortcut.** It works with Total Commander in front. Folderite uses the focused control to pick the panel, and the active panel when that isn't clear. + +## XYplorer + +Works in XYplorer (class `ThunderRT6FormDC`, process `xyplorer`). + +**What works.** Middle-click in the window. The folder you pick is sent to XYplorer as a `::goto` command. The **Open windows** section lists the folder of each XYplorer window. + +**How you can tell.** The top item is the folder from the title bar. XYplorer's default title is the path followed by the program name and version, and Folderite reads the path from that. + +**Not tested yet.** Folders with spaces in the path haven't been tried on a real XYplorer. The path is sent inside single quotes, which is how XYplorer scripts expect it, so it should work. It is unconfirmed. + +**Limits.** + +- If you changed XYplorer's title bar template so it no longer starts with the path, Folderite can't read the current folder. The menu has no current folder item, but picking a folder still works. +- Folderite sends one command to the window. It doesn't choose a pane. + +**Keyboard shortcut.** It works with XYplorer in front. + +## Blender + +Works in Blender's **File View** window: the file browser that opens for **File > Open**, **Import**, **Export** and folder pickers. It doesn't work in Blender's main window or in a file browser that is docked as an editor. + +**What works.** Middle-click on the bookmark list or on the file list. The folder you pick is typed into the window's directory field for you. The file name you had typed stays. + +**How you can tell.** There is no current folder item. Blender doesn't let Folderite read the folder, so the menu starts with your favorites. Folders you pick in Blender still go into your recent folders, but folders you only browse to are not recorded. + +**Limits.** Blender draws its own controls and reads the keyboard directly, so Folderite can't set the path through a window message. It does this instead: + +1. It copies the path to the clipboard, marked so clipboard history and clipboard managers ignore it. +2. It briefly moves the mouse pointer to the middle of the window and back. This takes a moment and the pointer doesn't visibly move. +3. It presses Ctrl+F, Shift+Tab, Ctrl+A, Ctrl+V and Enter, so Blender replaces the path in its directory field. +4. It puts your previous clipboard back. + +What this means for you: + +- Don't type or move the mouse while a folder is being sent. It takes under a second. Folderite refuses to send keys if Blender isn't the front window, so they can't land in another app. +- The clipboard is borrowed for that moment. Text comes back, and so do images (as bitmaps). Contents that are handles to graphics objects can't be saved and are not restored. If you copy something yourself during that moment, your copy is kept. +- If Blender isn't in front within half a second, the folder isn't sent and a balloon tells you. +- The window title has to be `Blender File View`. + +Blender 5.x is what this was written and tested against. + +**Keyboard shortcut.** It works with the File View window in front. The pointer still goes to the window's center and back while the folder is sent. + +## The desktop + +Middle-click on the desktop's icon area opens the menu. Picking a folder opens a new Explorer window, since the desktop has nowhere to navigate. + +**How you can tell.** The top item is `Desktop`. + +**Limits.** Turn this off with **Menu > Work on the desktop** in Settings (`enableOnDesktop` in `config.json`). It applies to the desktop only, not to Explorer windows. + +**Keyboard shortcut.** With the desktop in front, the shortcut opens the same menu. With any window Folderite doesn't support in front, the shortcut opens the menu without a current folder, and picking a folder opens a new Explorer window. The same happens when you click the tray icon. + +## Windows you run as administrator + +Folderite does nothing special for elevated windows. Windows doesn't let a program that isn't elevated watch or control one that is, so in an elevated Explorer window or file dialog the menu may not open, or a picked folder may not be used. If you need Folderite there, run Folderite as administrator. + +## Turn Folderite off for one app + +Add the app's program name to the list of disabled apps. Use the name without `.exe`, for example `notepad`. Case doesn't matter. + +- In Settings, open **Exclusions**. Under **Don't open the menu in these apps**, type the name and click **Add**, or pick from the running apps. +- In `config.json`, add it to `disabledProcesses`: + +```json +"disabledProcesses": ["notepad", "winword"] +``` + +The change applies right away. See [Settings](settings.md#exclusions) and [config.json](config.md). + +The name is the program that owns the window, not what it is called on screen. For an Open or Save dialog, it is the program that opened the dialog: `notepad` turns Folderite off in Notepad's dialogs. For Explorer windows and the desktop, it is `explorer`, so disabling `explorer` turns off both. + +Disabling an app also stops Folderite from recording that app's folders in your history and from moving its dialogs to a start folder. Pausing Folderite from the tray icon or from **General** in Settings stops the mouse and the keyboard shortcut for every app until you resume. diff --git a/docs/assets/icon.png b/docs/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e366e1ea9b3a14406f691472084c6c57ec6a2b80 GIT binary patch literal 20032 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 eVPSy_;eP@8g+GE;n|DnB0000 Personalization > Colors**). + +```bat +pwsh tools\screenshots\capture.ps1 +``` + +You need the .NET 10 SDK, `ffmpeg` on the PATH, a free `X:` drive letter and a primary monitor of at least 1600x1080. The script takes over the mouse and keyboard for about a minute, so leave them alone until it prints `done`. Before it starts, it quits a running Folderite, and it restarts that copy afterwards. Directory Opus should not be replacing Explorer during the run. + +The script hides the navigation pane in Explorer and file dialogs while it runs and restores the previous setting after. It also saves and puts back your clipboard. + +It writes `menu` and `settings` as PNG, `explorer` and `save-dialog` as GIF, and the `explorer-still` and `save-dialog-still` PNG frames. + +## Docs site + +The docs use MkDocs Material. The config is `mkdocs.yml`, the theme overrides are in `docs-theme/`, and the styles are in `docs/stylesheets/extra.css`. + +```bat +pip install -r requirements-docs.txt +mkdocs serve +mkdocs build --strict +``` + +`mkdocs serve` previews the docs with live reload. `mkdocs build --strict` fails on a broken link, so run it before you commit docs changes. + +`mkdocs build` writes to `site/.preview/docs`, which `mkdocs.yml` sets on purpose. MkDocs' default output folder is `site/`, which it empties first, and `site/` is the landing page's source here. Keep that `site_dir` line. + +The landing page source is in `site/`. To preview it together with the docs, run: + +```bat +pwsh site/preview.ps1 +``` + +It serves the landing page on http://127.0.0.1:8000/ and the docs under `/docs/`. Pass `-Port` to use another port. + +`.github/workflows/pages.yml` publishes both on a push to `main` that touches `site/`, the docs, the docs theme, `mkdocs.yml` or `requirements-docs.txt` (or when you run it by hand). A pull request that touches the docs only runs the strict build. + +## Releasing + +Push a version tag, and the [Release workflow](https://github.com/combwizard/Folderite/blob/main/.github/workflows/release.yml) does the rest: + +```bat +git tag v0.3.0 +git push origin v0.3.0 +``` + +The workflow runs the tests, publishes the app with the version from the tag, zips it as `Folderite--win-x64.zip` and attaches the zip to a draft release for that tag. Edit the notes, then publish the release. + +The version comes from the tag, not from `Folderite.csproj`. A tag with a suffix, such as `v0.3.0-beta.1`, makes a pre-release. If a release for the tag already exists, the zip is added to it. diff --git a/docs/config.md b/docs/config.md new file mode 100644 index 0000000..b7051e4 --- /dev/null +++ b/docs/config.md @@ -0,0 +1,251 @@ +# The config file + +Everything you set in [Settings](settings.md) is stored in one file, `config.json`. You can edit it by hand too. This page lists every key, the shape of the nested items, and what happens when something in the file is wrong. To find the file, see [Files and logs](files.md#where-the-files-are). + +## Format + +`config.json` is JSON with a few allowances: + +- `//` and `/* */` comments are allowed. +- Trailing commas are allowed. +- Key names are not case sensitive (`recentCount` and `RecentCount` are the same key). Settings writes them in camelCase, as shown on this page. +- Keys Folderite doesn't know are ignored. +- A key you leave out gets its default, listed below. +- Backslashes in paths must be doubled: `"C:\\Projects"`. + +Folderite reloads the file by itself. About 300 ms after a change on disk it reads the file again, and the new values apply to the next menu you open. The keyboard shortcut, the list of disabled apps and the history exclusions are applied at the same moment. You don't have to restart. The tray menu has **Reload** (and the folder menu has **Reload** at the bottom) to read the file again on demand. + +**Anything that saves the file rewrites it from scratch.** That happens when you change something in Settings, when you pick **Add to favorites** in the folder menu, and when Folderite creates the file on first run. The rewrite: + +- drops your `//` and `/* */` comments, +- drops keys Folderite doesn't know, +- writes the keys in a fixed order with two-space indentation, +- leaves out `items`, `showSubfolders` and `sortSubfolders` where they have no effect. + +If you keep notes in comments, keep them in a copy of the file, or edit by hand and don't touch Settings. Settings only saves when you change something, so opening it does not rewrite the file. + +## Keys + +| Key | Type | Default | Meaning | +| --- | --- | --- | --- | +| `favorites` | list of [favorite entries](#favorites) | `[]` (see below) | The favorites section of the menu. | +| `recentCount` | number | `15` | How many folders the **Recent** submenu lists. `0` hides the submenu. A negative number counts as `0`. Settings limits it to 0 to 50 (**Menu > Recent folders**). | +| `historyExclude` | list of text | `[]` | [Wildcard patterns](#history-exclusions) for folders that are never remembered. | +| `actions` | list of [actions](#actions) | one action, **Open &terminal here** | Commands in the current folder's submenu. | +| `disabledProcesses` | list of text | `[]` | Program names, without `.exe`, where Folderite does nothing. | +| `showOpenWindows` | true or false | `true` | Show the **Open windows** section. | +| `showClipboard` | true or false | `true` | Show the **Clipboard** item when the clipboard holds a folder path. | +| `enableOnDesktop` | true or false | `true` | Also open the menu on the desktop. Picking a folder there opens a new Explorer window. | +| `hotkey` | text | `""` | The [keyboard shortcut](#keyboard-shortcut) that opens the menu. Empty means none. | +| `showDrives` | true or false | `false` | Add a **Drives** submenu with the free space of each drive. | +| `dialogLastFolder` | true or false | `false` | Open new Open and Save dialogs in the folder last used in the same app. | +| `dialogFolders` | list of [dialog folders](#dialog-folders) | `[]` | A fixed start folder for the dialogs of one app. Wins over `dialogLastFolder`. | + +Some defaults need a note: + +- **`favorites`.** A key that is missing means no favorites. On first run, Folderite creates the file with four: `&Home` (your user folder), `&Desktop`, `Do&wnloads` and `D&ocuments`, each written as a full path. +- **`actions`.** A key that is missing gives the one default action, `Open &terminal here`, which runs `wt.exe` with `-d "{path}"`. To have no actions at all, write `"actions": []`. + +Where each key appears in Settings: `favorites` on **Favorites**, `actions` on **Actions**, `recentCount`, `showOpenWindows`, `showClipboard`, `showDrives` and `enableOnDesktop` on **Menu**, `dialogLastFolder` and `dialogFolders` on **Dialogs**, `historyExclude` and `disabledProcesses` on **Exclusions**, and `hotkey` on **General**. See [Settings](settings.md). + +## Favorites + +Each entry in `favorites` is one of three things. + +**A folder** has a `path`: + +```json +{ "name": "&Projects", "path": "D:\\Projects" } +``` + +**A separator** is an entry whose name is a single hyphen. It draws a line in the menu: + +```json +{ "name": "-" } +``` + +**A group** has `items`, a list of more entries of any kind. It becomes a submenu, and groups can hold groups: + +```json +{ "name": "Work", "items": [ + { "name": "Specs", "path": "%USERPROFILE%\\Work\\Specs" } +]} +``` + +| Key | Type | Meaning | +| --- | --- | --- | +| `name` | text | The text in the menu. Put `&` before a letter to make it the [accelerator key](#accelerators). A folder with an empty name shows the last part of its path. | +| `path` | text | The folder. [Environment variables](#environment-variables) are expanded. Folder entries only. | +| `items` | list of entries | Makes the entry a group. An entry with `items` is a group even when the list is empty, and its `path` is ignored. | +| `showSubfolders` | true or false | Folder entries only. Shows the folder as a submenu of its subfolders. Default `false`. | +| `sortSubfolders` | text | With `showSubfolders`: `"newest"` lists the most recently modified folders first. Anything else, or leaving it out, sorts by name. | + +How each kind is treated: + +- A folder entry with no `path`, or a blank one, is skipped. It does not show up in the menu. +- The `-` separator works anywhere in the tree, including inside groups. +- Folderite does not check that a folder exists when it builds the menu. Picking one that is gone fails (see [Troubleshooting](troubleshooting.md#a-folder-or-drive-is-not-available)). The Favorites page in Settings marks a missing local folder with "This folder doesn't exist." +- A group's `name` is shown as written, so a plain `&` in it becomes an accelerator. A name taken from a folder's path (when `name` is empty) has no accelerator, and any `&` in the folder name is shown as it is. + +### Subfolder submenus + +With `showSubfolders` on, the favorite is a submenu instead of an item. The first item in it is the folder itself, in bold, so you can still open it. Below a separator come its subfolders: + +- Hidden and system folders are left out. +- By name uses Explorer's order (`file2` comes before `file10`). `"sortSubfolders": "newest"` orders by last modified time, newest first. +- At most 100 subfolders are listed. If there are more, the last item is a grayed-out "Showing the first 100 folders". +- The folders are read when you open the submenu, not before. If the folder doesn't answer within 1.5 seconds (a sleeping network share, for example), the submenu shows the folder itself and no subfolders, and the menu does not freeze. + +`showSubfolders` and `sortSubfolders` on a group or a separator do nothing. + +## Actions + +Each action adds an item to the submenu that opens from the top item of the menu (the current folder). Actions are not shown when the menu has no current folder, for example when you open it from the tray icon. + +```json +{ "name": "Open in &VS Code", "command": "code", "args": "\"{path}\"" } +``` + +| Key | Type | Meaning | +| --- | --- | --- | +| `name` | text | The menu text. `&` marks the accelerator key. | +| `command` | text | The program or file to run. [Environment variables](#environment-variables) are expanded. | +| `args` | text | The arguments. Every `{path}` is replaced with the current folder's path, without quotes added, so write `\"{path}\"` in JSON when the path may contain spaces. | + +Folderite starts the command with the Windows shell, with the current folder as the working directory. That means a program name that is on your `PATH`, a full path, or a document all work. If it can't start, a balloon from the tray icon says "*name* failed:" followed by Windows' reason, with the `&` removed from the name. + +The submenu also holds two items of its own: **Copy path**, which is always there, and **Add to favorites**, which is hidden when the folder is already a favorite. They come from Folderite, not from `actions`. + +## Dialog folders + +`dialogFolders` sets the folder a new Open or Save dialog starts in, per app. See [Open and Save dialogs](dialogs.md). + +```json +"dialogFolders": [ + { "app": "notepad", "path": "D:\\Notes" } +] +``` + +| Key | Type | Meaning | +| --- | --- | --- | +| `app` | text | The program name of the app that opens the dialog, without `.exe`. A trailing `.exe` is tolerated here. Not case sensitive. | +| `path` | text | The start folder. [Environment variables](#environment-variables) are expanded. | + +An entry with an empty `app` or `path` is ignored (Settings drops it when it saves). The first entry whose `app` matches is used. If the folder doesn't exist, the dialog is left alone. + +## History exclusions + +Each pattern in `historyExclude` is matched against the whole folder path, with these rules: + +| Character | Matches | +| --- | --- | +| `*` | Any run of characters, including none and including `\`. | +| `?` | Exactly one character. | +| anything else | Itself. Case doesn't matter. | + +```json +"historyExclude": ["C:\\Windows\\*", "*\\node_modules\\*", "X:\\*"] +``` + +- The pattern must match the whole path. `C:\Windows\*` matches everything below `C:\Windows`, but not `C:\Windows` itself. Add a second pattern for that. +- Paths are compared without a trailing `\`, except for a drive root (`X:\`). +- Environment variables are not expanded in patterns. +- A blank pattern is ignored. +- A folder that matches is not recorded, and it is hidden from **Recent**. Folders recorded before you added the pattern stay in `history.json`, and show in **Recent** again if you remove the pattern. + +## Keyboard shortcut + +`hotkey` is text such as `"Win+Shift+F"` or `"Ctrl+Alt+F5"`. In Settings you record it by pressing the keys, so you rarely write it by hand. + +- Modifiers: `Ctrl` (or `Control`), `Alt`, `Shift`, `Win` (or `Windows`), joined with `+`, in any order. +- Keys: a letter, a digit, `F1` to `F24`, `Space`, `Insert`, `Delete` (or `Del`), `Home`, `End`, `PageUp`, `PageDown`, `Up`, `Down`, `Left`, `Right`, a punctuation key that has its own key on your keyboard layout, or a hex key code such as `0xC0`. +- Case doesn't matter, and spaces around `+` are fine. +- Exactly one key, and at least one modifier. Function keys are the only exception and can stand alone. + +If the text can't be read, or another program already owns the shortcut, Folderite shows a balloon when it starts or when the value changes, and Settings shows a warning. The exact messages are in [Troubleshooting](troubleshooting.md#the-keyboard-shortcut-does-nothing). + +## Environment variables + +Windows environment variables written as `%NAME%` are expanded in: + +- the `path` of a favorite, +- the `command` of an action (but not its `args`), +- the `path` of a dialog folder, +- the registry value that moves the config folder (see [Files and logs](files.md#registry-values)). + +A variable that doesn't exist is left as written, so the path won't resolve. Environment variables are not expanded in `historyExclude`. + +## Accelerators + +An `&` in a name marks the letter after it as the accelerator. With the menu open, press that letter to pick the item. Write `&&` for a literal `&`. + +- `"&Projects"` shows as Projects with the P underlined. Press **P**. +- `"Pro&jects && Notes"` shows as Projects & Notes, with the J as the accelerator. +- If only one item in a menu has that letter, pressing it picks the item. If several share it, each press moves to the next one, and **Enter** picks the highlighted one. +- An item with no accelerator can still be reached with the first letter of its text, when no item claims that letter. + +The folder menu's own items use accelerators too (**Copy path**, **Add to favorites**, **Recent**, **Drives**, **Clipboard**, **Settings…**, **Reload**), so pick your letters with those in mind where they share a menu. + +## Disabled apps + +`disabledProcesses` takes the program's file name without `.exe`, for example `"notepad"`. Folderite compares it with the name of the program that owns the window, ignoring case. A name written with `.exe` in the file does not match, so leave it off. (Settings removes the `.exe` when you add a name there.) + +An app on this list gets no menu from the mouse or the keyboard shortcut, and its folders are not recorded in your history. For Explorer windows and the desktop the name is `explorer`. See [Supported apps](apps.md#turn-folderite-off-for-one-app). + +## A full example + +Every key appears once. + +```jsonc +{ + "favorites": [ + // Its subfolders in a submenu, newest first. + { "name": "&Projects", "path": "D:\\Projects", "showSubfolders": true, "sortSubfolders": "newest" }, + { "name": "-" }, // separator + { "name": "Work", "items": [ // group: a submenu + { "name": "Specs", "path": "%USERPROFILE%\\Work\\Specs" } + ]} + ], + "recentCount": 15, + "historyExclude": ["C:\\Windows\\*", "*\\node_modules\\*"], + "actions": [ + { "name": "Open &terminal here", "command": "wt.exe", "args": "-d \"{path}\"" }, + { "name": "Open in &VS Code", "command": "code", "args": "\"{path}\"" } + ], + "disabledProcesses": ["someapp"], // program names without .exe + "showOpenWindows": true, + "showClipboard": true, + "enableOnDesktop": true, + "showDrives": false, // a Drives submenu with free space + "hotkey": "Win+Shift+F", // keyboard shortcut for the menu; "" = none + "dialogLastFolder": false, // open file dialogs in the folder last used in that app + "dialogFolders": [ // fixed start folders for one app's file dialogs + { "app": "notepad", "path": "D:\\Notes" } + ] +} +``` + +## When the file has a mistake + +How Folderite treats a bad file depends on what is wrong. + +| What is wrong | What happens | +| --- | --- | +| Not valid JSON: a missing comma or brace, an unquoted key, single quotes, a single backslash in a path | The whole file is rejected. Nothing from it is used. | +| A value of the wrong type, such as `"recentCount": "ten"` or `"showDrives": "yes"` | The same: the whole file is rejected. | +| An empty file | The same. | +| A key Folderite doesn't know, or a different capitalization of a known key | The unknown key is ignored. A known key in another capitalization is read normally. | +| A number out of range, such as a negative `recentCount` | The value is accepted and treated as the nearest sensible one (`0`). | +| A favorite with no `path` and no `items`, or a blank `path` | That entry is skipped. The rest of the file works. | +| A path that doesn't exist | Accepted. The menu shows the item, and picking it fails when the window can't go there. | +| An unreadable `hotkey` | The rest of the file works. Only the shortcut is not set, and a balloon says so. | + +When the file is rejected: + +- **Folderite keeps using the last good settings.** If this happens at startup, there are no earlier settings, so Folderite runs with built-in defaults: no favorites, and the one default action. +- **The error message comes from the JSON reader.** It names the position, for example the line number and the path of the key. A balloon with the title "Folderite: config error" shows it at startup and when you pick **Reload**. A change you save from your editor while Folderite runs is checked silently, and the balloon is not shown. The message is not written to `folderite.log`. +- **Settings stops saving.** The Settings window shows "config.json has an error, so changes here are not saved." with the message under it and the buttons **Open file** and **Reload**. This protects the file you are in the middle of fixing, because Settings would otherwise overwrite it with its own copy. Fix the file and save it, and Settings picks the change up on its own, or press **Reload**. + +See [Troubleshooting](troubleshooting.md#settings-says-configjson-has-an-error) for the steps. + +If you delete `config.json` while Folderite runs, it keeps working with what it has in memory. The next time it reads the file (**Reload** from the tray menu, or a restart), it creates the file again with the four default favorites. diff --git a/docs/dialogs.md b/docs/dialogs.md new file mode 100644 index 0000000..f9bd945 --- /dev/null +++ b/docs/dialogs.md @@ -0,0 +1,100 @@ +# Open and Save dialogs + +Folderite works in the standard Windows Open, Save As and folder-picker dialogs. Middle-click in one, pick a folder from the menu, and the dialog goes there. + +![Middle-clicking in a Save As dialog and picking Work > Reports; the typed file name stays](images/save-dialog-light.gif#only-light) +![Middle-clicking in a Save As dialog and picking Work > Reports; the typed file name stays](images/save-dialog-dark.gif#only-dark) + +## Which dialogs count + +A window counts as a file dialog when it is a standard Windows dialog (window class `#32770`) that hosts a file list. That covers the Open, Save As and folder-picker dialogs of programs such as Notepad, Office and browsers, and of anything else that calls the Windows common dialog. Dialogs that a program draws itself do not match, and neither do message boxes. + +The menu opens when you middle-click anywhere in the dialog, or press the [keyboard shortcut](settings.md#keyboard-shortcut) while the dialog is in front. A program in the [**Don't open the menu in these apps**](settings.md#dont-open-the-menu-in-these-apps) list is ignored, dialogs included. + +Blender's file browser is not one of these dialogs. It has its own support, described in [Supported apps](apps.md). + +## Reading the current folder + +Folderite reads the folder from the address bar of the dialog (the breadcrumb). Two things follow from that: + +- The menu header shows the folder the dialog is in, with **Copy path**, your [actions](settings.md#actions) and **Add to favorites**. +- If the dialog is somewhere that is not a folder path, such as **This PC** or a library, there is no current folder. The menu still opens and navigation still works, but the current-folder submenu is missing, and so are the actions. + +The folder you were in is also recorded in [history](settings.md#history) under the program's name, which is how **Recent** favors the folders you use in that program. + +## Navigating and keeping your file name + +Pick a folder in the menu and the dialog moves there. If you had already typed a file name, it stays: + +1. Folderite puts the folder path in the **File name** box and presses the dialog's **Open** or **Save** button. The dialog treats a folder path as "go there". +2. It waits until the dialog shows the new folder. +3. It puts your text back in the **File name** box. + +The text is only restored if the box is empty or still holds the folder path. If you changed the box while the dialog was moving, Folderite leaves it alone. If you had typed nothing, the box stays empty. + +## Folder pickers + +A folder picker (the dialog that asks you to choose a folder) has no file type box, and pressing its confirm button would accept the folder instead of opening it. So Folderite types the path into the dialog's address bar and presses **Enter** there, which opens the folder without choosing it. If it can't find the address bar, a tray notification says `Couldn't open in File dialog.` + +## Start folders + +Normally a dialog opens wherever the program or Windows decides. Folderite can move each new dialog to a folder of your choice, as soon as it appears. There are two ways to say which folder, both on the [Dialogs page](settings.md#dialogs) in Settings: + +- **Start in the last folder used** (`"dialogLastFolder": true`, off by default) starts a program's dialogs in the folder you last used in that program. +- **Folders for specific apps** (`"dialogFolders"`) sets a fixed folder for named programs. + +In `config.json`: + +```jsonc +{ + "dialogLastFolder": true, + "dialogFolders": [ + { "app": "notepad", "path": "D:\\Notes" }, + { "app": "winword", "path": "%USERPROFILE%\\Documents\\Reports" } + ] +} +``` + +See [Config file](config.md) for the file format. + +### Which folder wins + +For each new dialog, Folderite goes through these in order and uses the first that gives a folder: + +1. **A folder for that app.** The first row in `dialogFolders` whose app matches the program's file name (ignoring case and any `.exe`) and whose path is not empty. This applies even when **Start in the last folder used** is off. +2. **The last folder used in that app**, if **Start in the last folder used** is on. This is the folder in [history](settings.md#history) that was most recently recorded for that program, skipping folders matched by an [exclusion pattern](settings.md#dont-remember-these-folders). Folderite records the folders of file dialogs in history whether or not the option is on, so it works as soon as you turn it on. A program whose dialogs Folderite has never seen has nothing to go on. +3. Nothing. The dialog opens where it would have opened anyway. + +`%` environment variables in a `dialogFolders` path are expanded. + +### Which apps + +Any program whose dialogs Folderite recognizes. The program is identified by its file name without `.exe`, lower-cased: `notepad`, `winword`, `chrome`. That is the name you put in the **App** box. The [Exclusions](settings.md#dont-open-the-menu-in-these-apps) list applies: no start folder is set for a listed program. + +### When it does not apply + +Folderite leaves the dialog alone when: + +- No folder is found by the rules above. +- The folder does not exist. This is checked once, when the dialog appears, so a drive that is not ready yet means no move. +- The dialog is already in that folder. +- The program already put a folder in the **File name** box (a path with `\` or `/` in it), which means it chose where to save. +- The dialog does not show a readable folder within about 3 seconds. Folderite then gives up on that dialog. If a folder picker is showing but Folderite can't drive its address bar in that time, it writes `Couldn't move the file dialog to .` to `folderite.log`. + +Each dialog is handled once, the first time it comes to the front. After that it is yours: Folderite does not pull it back if you browse elsewhere. Pausing Folderite (the tray menu, or **Pause Folderite** on the General page) stops the middle-click menu but does not stop start folders. + +Moving the dialog uses the same steps as the menu, so it also keeps a file name that was already typed. + +## Elevated dialogs + +Windows does not let a normal program control a window that runs as administrator, and Folderite follows that rule. Unless Folderite itself runs elevated, it can't navigate a dialog from an elevated program, so picking a folder does not move the dialog, and a start folder can't move it either. Dialogs in programs started normally are not affected. + +## Known limits + +- Only standard Windows file dialogs work. Dialogs a program draws itself are not recognized. +- The dialog must show a real folder to give the menu a current folder. Places such as **This PC** have none. +- Start folders move the dialog after it appears, so you may see it open in its usual folder and then jump. A dialog that is slow to load can take up to about 3 seconds. +- Start folders need the folder to exist when the dialog opens. +- Elevated dialogs are out of reach unless Folderite runs elevated too. + +If a dialog does not respond, see [Troubleshooting](troubleshooting.md). diff --git a/docs/files.md b/docs/files.md new file mode 100644 index 0000000..504c8c7 --- /dev/null +++ b/docs/files.md @@ -0,0 +1,123 @@ +# Files and logs + +Folderite keeps three files, always together in one folder, and writes only two registry values. + +| File | What it holds | +| --- | --- | +| `config.json` | Your settings. See [The config file](config.md). | +| `history.json` | The folders you visit, with how often and how recently, for the **Recent** submenu. | +| `folderite.log` | Errors and a few notices, for bug reports. It only exists once something has been logged. | + +## Where the files are + +Folderite picks the folder in this order and uses the first that applies: + +1. **Next to `Folderite.exe`**, if a `config.json` is there. This is [portable mode](#portable-mode). +2. **The folder in the registry**, if the value `ConfigDirectory` under `HKEY_CURRENT_USER\Software\Folderite` is set and not blank. Settings sets it when you use **Change location…**. Environment variables such as `%OneDrive%` in it are expanded. +3. **`%APPDATA%\Folderite`**, which is `C:\Users\\AppData\Roaming\Folderite`. This is the default. + +The rule looks for `config.json` only in the first case. A folder named in the registry doesn't need a `config.json` yet: Folderite creates the folder and a new file with default settings. + +All three files always sit in the same folder. The log follows the config, including in portable mode, where it is written next to `Folderite.exe`. + +To see where you are, open **Settings > General**. The **Config file** card shows the full path, with a caption that says **Default location**, **Custom location** or **Portable**. **Open file** and **Open folder** are on the same card, and the tray menu has **Edit config…** and **Open config folder**. + +Folderite finds the folder when it starts. If you change where the files are by hand (create a `config.json` next to the exe, or edit the registry value), quit Folderite from the tray and start it again. + +## Moving the files + +On **Settings > General**, the **Config file** card has two buttons: + +- **Change location…** asks for a folder ("Choose a folder for Folderite's settings") and moves the files there. Folderite remembers the folder in the registry. +- **Use default** moves them back to `%APPDATA%\Folderite`. It shows only when the files are somewhere else. + +Moving stops Folderite, moves the files, and starts it again, then reopens Settings with a note such as "Settings moved to D:\Sync\Folderite". The Settings window is disabled meanwhile. Nothing is deleted from the old folder until every file has been copied, so a failure leaves your settings where they were. If Folderite doesn't close within a few seconds, you get "Folderite didn't close, so the settings were not moved." and nothing changes. Any other failure shows its Windows error and reopens Settings on the old location. + +`config.json`, `history.json` and `folderite.log` all move. + +### When the new folder already has settings + +If the folder you pick already holds a `config.json`, Folderite asks: + +> *folder* already has Folderite settings. +> +> Yes: use the settings in that folder. +> No: replace them with your current settings (the old file is kept as config.json.bak). + +| Answer | Result | +| --- | --- | +| **Yes** | You switch to the settings in that folder. Your current `config.json` is left behind in the old folder. Your history is copied over only if the new folder has none; otherwise it stays behind too. | +| **No** | The folder's `config.json` is renamed to `config.json.bak` and yours is copied in. `history.json` gets the same treatment when you have one to copy. | +| **Cancel** | Nothing happens. | + +Whenever a `.bak` file already exists it is replaced. The log is moved in every case and overwrites a `folderite.log` already in the new folder, without a backup. + +## Portable mode + +A `config.json` next to `Folderite.exe` always wins over the registry and over `%APPDATA%`. With that file in place, Folderite is portable: the settings, history and log travel with the folder, for example on a USB stick, and the registry value and `%APPDATA%` are ignored. + +To turn it on, choose Folderite's own folder in **Change location…** and answer **OK** to: + +> *folder* is Folderite's own folder. Settings stored there make Folderite portable: it uses them wherever the folder goes, for example on a USB stick. +> +> Make Folderite portable? + +The registry value is removed in the process, so the folder alone decides. You can also copy a `config.json` next to the exe yourself and restart Folderite. + +While it is portable, the **Config file** card shows a **Portable** badge and the note "Portable mode: the config lives next to Folderite.exe. Change location or Use default moves it out." + +To leave portable mode, use **Use default** or **Change location…** to a different folder. If you answered **Yes** to using settings that were already in the other folder, your portable `config.json` is renamed to `config.json.bak`, because it would otherwise keep winning. + +Two cautions: + +- The folder must be writable. In a protected folder such as `C:\Program Files`, Settings can't save and shows "Not saved:" with the reason, and the log can't be written. +- **Start with Windows** stores the exe's full path (see below). If you move a portable folder to another drive letter or path, turn the option off and on again. + +## Backing up + +Copy `config.json` for your settings and `history.json` for your recent folders. That's all. Restore them by putting them back in the folder from [Where the files are](#where-the-files-are), or move to a new PC by pointing a new install at a folder that holds them (see above). + +- Quit Folderite from the tray first if you want the latest history. It writes `history.json` about every 60 seconds when something changed, when you quit, and when you open the **History** page in Settings. A copy taken while it runs can miss the last minute. +- `history.json` is rebuilt as you work. If it can't be read, Folderite starts with an empty history, and your next save replaces the file. Deleting it while Folderite is quit clears your history (the **History** page in Settings can also remove entries one by one). +- You may see short-lived files named like `config.json..tmp`. Folderite writes the new file under that name and then renames it over the old one, so a crash can't leave a half-written `config.json`. +- To start over, quit Folderite, delete `config.json` and start it again. The default file is created. + +## The log + +`folderite.log` is a plain text file. Each entry is one line, or a few for an error with a stack trace, starting with the local date and time: + +``` +2026-11-04 09:15:02 INFO Win+Shift+F is already used by another app. Pick another shortcut in Settings › General. +2026-11-04 09:20:41 ERROR System.IO.IOException: ... +``` + +`INFO` lines are notices. `ERROR` lines are unexpected exceptions with their details. What gets written: + +- **Startup failures.** If Folderite can't start (for example the mouse hook can't be installed), the error is logged and it exits. +- **Unexpected errors** while handling a click, a window event, the open-window list, a subfolder submenu or the **Drives** submenu. Ordinary conditions such as an offline folder or a missing drive are not logged. +- **Keyboard shortcut problems.** The two messages listed under [Troubleshooting](troubleshooting.md#the-keyboard-shortcut-does-nothing). +- **Dialogs.** "Couldn't move the *program* file dialog to *folder*." when a dialog never took the start folder you set. +- **The menu.** "Menu owner could not take the foreground; outside clicks may not dismiss the menu." + +Not in the log: config errors (they appear in a balloon and in Settings), the folders you visit, and file names. + +**Size.** The file is appended to. Before each write, Folderite checks it, and when it is larger than 1,000,000 bytes (about 1 MB) it deletes the whole file and starts a new one. So the log never grows past roughly 1 MB, and after a reset the older entries are gone. Copy the file first if you need them. + +If the folder can't be written to, logging stops silently. It can't cause an error of its own. + +You can delete the log any time. It comes back when the next entry is written. + +## Registry values + +Folderite writes two values, both under `HKEY_CURRENT_USER`, so no administrator rights are needed. + +| Key | Value | Written when | Content | +| --- | --- | --- | --- | +| `Software\Folderite` | `ConfigDirectory` | You move the files with **Change location…** to a folder that is not the default and not Folderite's own. | The full path of the folder (a string). | +| `Software\Microsoft\Windows\CurrentVersion\Run` | `Folderite` | **Start with Windows** is switched on, in Settings or the tray menu. | The full path of `Folderite.exe` in quotes. | + +Both values are removed when you switch the option off, or, for `ConfigDirectory`, when you choose **Use default** or Folderite's own folder. Folderite never writes to `HKEY_LOCAL_MACHINE`. + +The `Run` value holds the path as it was when you switched the option on. If you move or rename the Folderite folder, switch **Start with Windows** off and on again from the new location. + +To remove every trace, switch **Start with Windows** off, choose **Use default** in Settings, quit Folderite, and delete the folder. Then delete `%APPDATA%\Folderite` if you want your settings gone as well. See [Install and uninstall](install.md). diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..9ee8ea4 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,80 @@ +# Getting started + +This page takes you from a fresh start to your first menu, your first favorite, and a Save dialog that jumps to a folder. It assumes Folderite is [installed](install.md). + +## What happens on first run + +Run `Folderite.exe`. There is no setup window and no welcome screen. Two things happen: + +- A Folderite icon appears in the notification area. Hover it to see the version. +- Folderite creates `%APPDATA%\Folderite\config.json` with a starter setup, and starts watching which folders you use. + +The starter setup gives you four favorites (**Home**, **Desktop**, **Downloads**, **Documents**) and one action (**Open terminal here**). It shows open windows and the clipboard folder, and leaves the Drives section off. It sets no keyboard shortcut. [Install and uninstall](install.md#first-run) has the full list. + +If `config.json` has an error when Folderite starts or when you choose **Reload**, Folderite shows a "Folderite: config error" notification with the message and does not use the broken file. + +## Your first middle-click + +1. Open an Explorer window. +2. Middle-click anywhere in it, such as the empty part of the file list. On Windows 11, do not click the tab row, because middle-clicking a tab still closes it. Everything below the tab row counts. +3. The folder menu opens at the pointer. + +![The Folderite menu over an Explorer window: the current folder, favorites, open windows, recent folders and the clipboard folder](images/menu-light.png#only-light) +![The Folderite menu over an Explorer window: the current folder, favorites, open windows, recent folders and the clipboard folder](images/menu-dark.png#only-dark) + +From the top, the menu shows the folder you are in (as a submenu with **Copy path** and your actions), your favorites, **Open windows** with the folders open in other windows, a **Recent** submenu, the **Clipboard** folder when you have copied a folder path, and **Settings…** and **Reload**. On a fresh install, **Recent** is empty and hidden until Folderite has seen you visit a folder besides the one you are in. [The folder menu](menu.md) explains each section and when it shows. + +### Pick a folder + +Click a folder, or press the letter underlined in its name. With the starter favorites, **W** picks Downloads and **O** picks Documents. **D** is shared with **Reload**, so it highlights Desktop first, and **Enter** picks it. The window you clicked in goes to that folder. In Explorer with tabs, the tab you are in is the one that changes. + +To close the menu without picking, press **Esc** or click away. + +![Middle-clicking in Explorer, picking Work, Specs, then a recent folder](images/explorer-light.gif#only-light) +![Middle-clicking in Explorer, picking Work, Specs, then a recent folder](images/explorer-dark.gif#only-dark) + +## Add your first favorite + +There are two ways. Both save right away. + +**From the menu.** Middle-click while you are in the folder you want. Point at the first item, the one named after the current folder. In its submenu, choose **Add to favorites**. The folder goes to the end of your favorites list. The item is not there when the folder is already a favorite. + +**From Settings.** + +1. Open Settings: right-click the tray icon and choose **Settings…**, or choose **Settings…** at the bottom of the folder menu. +2. Stay on **Favorites**, the first page. +3. Click **Add folder** and choose one or more folders. You can also drag folders from Explorer into the list. +4. Select a favorite to change its **Name** or its **Folder**. Put `&` before a letter in the name to make it the shortcut key, for example `&Projects`. +5. Click **Preview menu** to see the real menu with your changes. + +![The Settings window on the Favorites page](images/settings-light.png#only-light) +![The Settings window on the Favorites page](images/settings-dark.png#only-dark) + +Changes save as you make them, and the running Folderite picks them up straight away. [Settings](settings.md) covers every page, including groups, separators and subfolder submenus. + +## Use it in a Save dialog + +Folderite works in Open and Save dialogs the same way. + +1. In any app, open a Save As or Open dialog, for example in Notepad. +2. Middle-click anywhere in the dialog. +3. Pick a folder. The dialog jumps there. + +If you had typed a file name already, it stays. + +![Middle-clicking in a Save As dialog and picking Work, Reports; the typed file name stays](images/save-dialog-light.gif#only-light) +![Middle-clicking in a Save As dialog and picking Work, Reports; the typed file name stays](images/save-dialog-dark.gif#only-dark) + +Folder pickers, such as "Select Folder" dialogs, work too: Folderite navigates the picker instead of accepting a folder. Folderite can also start new dialogs in a folder you choose. See [Dialogs](dialogs.md). + +## If you have no middle button + +Left-click the tray icon to open the menu. Or set a keyboard shortcut under **Settings**, **General**, **Keyboard shortcut**. The shortcut opens the menu for the window in front. See [The folder menu](menu.md#keyboard-shortcut). + +## Where to go next + +- [The folder menu](menu.md): every section of the menu, how recent folders are ranked, and where middle-click is left alone. +- [Settings](settings.md): favorites, actions, menu sections, exclusions and the rest. +- [Dialogs](dialogs.md): start folders for Open and Save dialogs. +- [Supported apps](apps.md): Explorer, file dialogs, Directory Opus, Total Commander, XYplorer, Blender and the desktop. +- [Config reference](config.md): the `config.json` keys, if you prefer to edit the file. diff --git a/docs/how-it-works.md b/docs/how-it-works.md new file mode 100644 index 0000000..c2abdff --- /dev/null +++ b/docs/how-it-works.md @@ -0,0 +1,233 @@ +# How it works + +This page follows one middle-click from the mouse to the folder change, then covers the parts around it: history, the menu, the Settings process and config reload. Paths such as `src/Folderite/App.cs` are relative to the repository root. + +## The pieces + +| Piece | File | What it does | +| --- | --- | --- | +| Mouse hook | `src/Folderite/Hooks/MouseHook.cs` | Global middle-click hook on its own thread. Swallows the click and its release when the window is supported. | +| Window to adapter | `src/Folderite/Targets/TargetRegistry.cs` | Turns the window under the cursor into an adapter, or nothing. Applies the disabled-apps list. | +| Explorer | `src/Folderite/Targets/ExplorerAdapter.cs` | `Shell.Application` COM. The active Windows 11 tab is found through `ShellTabWindowClass`. | +| File dialogs | `src/Folderite/Targets/FileDialogAdapter.cs` | Reads the breadcrumb caption. Navigates through the file name box and OK, or through the address bar for folder pickers. | +| Directory Opus | `src/Folderite/Targets/ThirdParty/DopusAdapter.cs` | Runs `dopusrt.exe`. | +| Total Commander | `src/Folderite/Targets/ThirdParty/TotalCommanderAdapter.cs` | `WM_USER+50` queries to read, `WM_COPYDATA` with a `CD` command to navigate. | +| XYplorer | `src/Folderite/Targets/ThirdParty/XYplorerAdapter.cs` | Reads the title bar, navigates with a `::goto` script sent by `WM_COPYDATA`. | +| Blender | `src/Folderite/Targets/ThirdParty/BlenderAdapter.cs` | Synthesized keys into the File View window, with the clipboard borrowed. Can't read the folder. | +| Desktop | `src/Folderite/Targets/DesktopAdapter.cs` | Recognizes the desktop's icon area. "Navigating" opens a new Explorer window. | +| History | `src/Folderite/History/HistoryTracker.cs`, `FrecencyStore.cs` | WinEvent hooks for name and foreground changes, zoxide-style frecency. | +| Menu | `src/Folderite/Menu/` | A menu tree built from config and context, drawn as an owner-drawn native popup. | +| Settings | `src/Folderite/Settings/` | `Folderite.exe --settings`: a WPF window in a separate process. | +| IPC | `src/Folderite/Interop/Ipc.cs` | Settings to tray commands over `WM_COPYDATA`. | +| Wiring | `src/Folderite/App.cs` | The hidden message window that connects everything. | + +## The tray app + +`src/Folderite/Program.cs` takes a mutex (`Local\Folderite.SingleInstance`) so only one copy runs, then starts `App` (`src/Folderite/App.cs`). `App` creates a hidden top-level window, loads config and history, builds the adapter list, installs the hooks and runs a normal message loop. + +The window is a real hidden window, not a message-only one, because `TrackPopupMenu` needs an owner that can take the foreground. Everything that has to happen on the main thread arrives at this window as a message: the hook's trigger, the tray icon, timers, the keyboard shortcut, IPC and config reloads. Work from other threads goes onto a queue and wakes the window with a posted message. + +The tray app never loads WPF. The Settings window lives in its own process (see [below](#the-settings-process)), and `Program.cs` keeps that code out of `Main` so the tray app doesn't even load its types. + +## The mouse hook + +`MouseHook` installs a `WH_MOUSE_LL` hook. A low-level hook runs inside the thread that installed it, and Windows gives it a short time to answer. If a callback is slow, input stops for the whole system, and Windows can remove the hook without telling you. So the hook has its own thread with nothing on it except a message loop, and its callback does very little. + +On a middle-button press, the callback: + +1. Finds the window under the cursor and its top-level window. +2. Asks `ShouldTrigger`, which is `_registry.Find(...)` from `App.Run`, whether that window belongs to an adapter. +3. If yes, remembers the exact window under the cursor, posts a `WM_APP+1` trigger message to the tray app's window with the top-level window and the packed screen point, and returns without passing the click on. + +The slow work (COM calls, `dopusrt.exe`, building the menu) happens when the main thread handles that message. + +The callback swallows the button press and, later, the matching release. If it ate only the press, the app under the cursor would see a release with no press, which some programs treat as a click or a drag end. When the press is swallowed, a flag is set, and the next middle-button release is swallowed too. Clicks on windows that don't belong to an adapter pass through untouched, as do all clicks while Folderite is paused. + +## From window to adapter + +`TargetRegistry.Describe` turns a window handle into a `TargetWindow`: the top-level window, the exact child under the cursor, the window class, the lower-case process name without `.exe`, and the click point. Process names are cached per process id, so the hook doesn't look up the process image on every click. + +`TargetRegistry.Find` then does two things: + +1. If the process name is in `disabledProcesses`, the answer is nothing. +2. Otherwise, it asks each adapter in order whether it `Matches` the window and returns the first that says yes. + +The order is set in `App.Run`: Explorer, file dialogs, Directory Opus, Total Commander, XYplorer, Blender, desktop. `Matches` runs on the hook thread, so it has to be cheap: class names, process names, and at most a `FindWindowEx` or two. No COM and no process launches. + +Every adapter implements `ITargetAdapter` (`src/Folderite/Targets/ITargetAdapter.cs`): `Matches`, `GetCurrentPath`, `Navigate` and, for the **Open windows** section, `EnumerateOpenPaths`. + +## The adapters + +### Explorer + +`ExplorerAdapter` matches the classes `CabinetWClass` and `ExploreWClass`, except over the tab row. On Windows 11, the tabs share the title bar row with a window of class `TITLE_BAR_SCAFFOLDING_WINDOW_CLASS`. A click above the bottom edge of that window passes through, so a middle-click on a tab still closes it. A window without that child has no tabs, so there is no exemption. The keyboard shortcut has no click point, so it skips this check. + +Folderite reads and drives Explorer through the `Shell.Application` COM object, late-bound, and its `Windows()` list. On Windows 11, every tab is its own entry in that list, and they all report the HWND of the frame window. The active tab is the entry whose own window matches the first `ShellTabWindowClass` child of the frame. Folderite gets each entry's tab window through `IServiceProvider` and `SID_STopLevelBrowser`. If the frame has no such child (an older Explorer), the first entry for that frame is used. + +- **Read:** the entry's `Document.Folder.Self.Path`. A path that starts with `::` is a virtual folder and counts as no path. +- **Navigate:** `Navigate2(path)` on the entry. +- **Open windows:** the path of every entry. + +### File dialogs + +`FileDialogAdapter` matches a `#32770` dialog that has a `DUIViewWndClassName` or `SHELLDLL_DefView` child. + +- **Read:** the dialog's address bar is a breadcrumb toolbar whose caption reads `Address: C:\...`, with the word in the user's language. Folderite finds the `ToolbarWindow32` whose parent is `Breadcrumb Parent`, cuts the text after the first `: `, and keeps it if it's a rooted path. +- **Navigate in an Open or Save dialog:** put the path into the file name box and send OK, the same as typing a folder into the box and pressing Enter. The dialog treats a folder as "go there". The old text of the box is saved first. The dialog clears the box once it arrives, so Folderite waits (up to about a second) until the breadcrumb shows the new folder and then puts your text back, unless you've typed something else. +- **Navigate in a folder picker:** a dialog without a file-type box is a folder picker. OK there would accept the current folder, so Folderite types the path into the address bar's edit control and sends Enter to that control directly. A posted Enter would go through the dialog's own key handling and turn into OK. + +`FileNameHasFolder` reports whether the file name box already holds a path, which means the app chose where to save. The dialog start folder feature uses it to leave such dialogs alone. + +### Directory Opus + +`DopusAdapter` matches the class `dopus.lister` and drives `dopusrt.exe`, found next to the running Opus or in the default install folder. + +- **Read:** `dopusrt /info ,paths` writes an XML file with one `` for each tab: the lister and tab window handles, the side, which tab is active, and whether a tab is the source or destination. Folderite picks the tab that contains the window under the cursor, else the source tab, else the active one. +- **Navigate:** `dopusrt /acmd Go ""`. `Go` acts on the source tab of the most recently active lister. If the tab you clicked is the destination one, Folderite first runs `Set FOCUS=left` or `Set FOCUS=right` to make it the source. +- **Open windows:** the same query, de-duplicated. If no lister exists, `dopusrt.exe` isn't started. + +Every call to `dopusrt.exe` is capped at two seconds. Arguments are passed as one raw string, because Opus parses its own quotes. + +### Total Commander + +`TotalCommanderAdapter` matches the class `TTOTAL_CMD`. + +- **Read:** send `WM_USER+50` with `wParam` 9 or 10 to get the window handle of the left or right path label, then read its text. A trailing `*.*` or `>` is dropped, and the result has to be an existing folder. `wParam` 1000 asks which panel is active. +- **Which panel:** the panel whose path label overlaps the clicked control horizontally and sits closest above it. This handles side-by-side and stacked layouts. If two panels fit equally well, it counts as unknown. +- **Navigate:** a `WM_COPYDATA` message with the code `CD`, carrying the left path, a line break, and the right path in UTF-8 with a byte-order mark for each. If the clicked panel is the left one, only the left path is sent. For the right one, the left path is empty. If the panel is unknown, an `S` flag tells Total Commander that the path is for the source panel. + +### XYplorer + +`XYplorerAdapter` matches the class `ThunderRT6FormDC` in a process named `xyplorer`. + +- **Read:** XYplorer's default title is ` - XYplorer `. Folderite takes the longest prefix ending at a ` - ` that is an existing folder, so paths that contain ` - ` still work. +- **Navigate:** a `WM_COPYDATA` message with the id `0x00400001` and the UTF-16 script `::goto 'path';`. A single quote in the path is doubled. + +### Blender + +Blender draws its own interface and reads the keyboard through raw input, so there are no controls to talk to and posted key messages are ignored. `BlenderAdapter` matches a window of class `GHOST_WindowClass` in the process `blender` whose title is `Blender File View`. It can't read the folder, so `GetCurrentPath` is always empty. + +To navigate, it needs real input: + +1. Copy the path to the clipboard, marked as excluded from clipboard history, after taking a copy of everything that is on it. +2. Wait up to half a second for Blender to become the foreground window. +3. In one batch of `SendInput`, move the pointer to the middle of the window, press Ctrl+F, and move the pointer back. Blender sends keys to the area under the mouse, and Ctrl+F over a bookmark list opens that list's own filter. Ctrl+F starts editing the filter box in the path bar. +4. Press Shift+Tab to move to the directory field. +5. Press Ctrl+A, Ctrl+V and Enter to replace its text with the path. +6. After a short pause so Blender has read the clipboard, restore the old clipboard, unless the clipboard changed in the meantime. + +After each batch, Folderite waits until Blender has handled it, then checks that Blender is still in front. If not, it stops. The keys go out only while Blender has the foreground, so they can't land in another app. The scan code is filled in as well as the virtual key, because Blender reads both. + +### Desktop + +`DesktopAdapter` matches the classes `Progman` and `WorkerW` when the window under the cursor is `SysListView32` or `SHELLDLL_DefView`, and `enableOnDesktop` is on. The current folder is the Desktop folder. "Navigating" starts `explorer.exe` with the path. + +## The keyboard shortcut + +The shortcut is an ordinary `RegisterHotKey` on the hidden window, with the no-repeat flag, parsed by `src/Folderite/Hooks/Hotkey.cs`. When it fires, `App.OnHotkey` takes the foreground window and its top-level window, and finds the control that has the keyboard focus (`GetGUIThreadInfo`). That control stands in for the window under the cursor that a click would give. Opus and Total Commander use that control to choose the pane or panel. The registry resolves it like any other window, with the same adapters and the same disabled-apps list. With no match, the tray menu opens instead: no current folder, and picking a folder opens a new Explorer window. + +## The trigger, step by step + +For a click on a supported window: + +1. The hook thread sees the middle-button press, finds the adapter, swallows the click and posts a trigger message. +2. The main thread resolves the window again with the click point and gets the adapter. (`App.OnTrigger`.) +3. `App.ShowFolderMenu` asks the adapter for the current folder, and records it in history. It collects the open windows from every adapter, the top recent folders, and the folder on the clipboard. +4. `MenuComposer.Build` makes a menu tree from the config and this context. +5. `MenuBuilder.Show` shows it and waits for you to pick. +6. The chosen item's action runs after the menu is gone. For a folder, `App.Navigate` brings the target window to the front, calls the adapter's `Navigate`, and records the folder in history for that app. If `Navigate` fails, a tray balloon says so. + +## History + +`HistoryTracker` records folders without polling. It installs two out-of-context WinEvent hooks, skipping Folderite's own process: + +- `EVENT_SYSTEM_FOREGROUND`: a window came to the front. +- `EVENT_OBJECT_NAMECHANGE`: a window's name changed. Explorer, Opus and XYplorer put the folder in the title, and Total Commander changes the text of its path label, so folder changes show up this way. + +For each event on a top-level window, the tracker asks the registry whether an adapter handles the window. If so, it adds the window to a pending set and starts a 500 ms timer. When the timer fires, the tracker asks each pending window's adapter for its current folder and records it. This is a debounce: a burst of name changes while you click through folders produces one read. + +Folderite also records the folder when the menu opens, and the folder you picked afterward. The foreground event is also passed on, immediately, to the dialog start folder code (`src/Folderite/Dialogs/DialogStarter.cs`), so it can move a new Open or Save dialog before history records the folder the dialog opened in. + +### Frecency + +`FrecencyStore` (`src/Folderite/History/FrecencyStore.cs`) keeps one entry per folder (case-insensitive), with a rank and the time of the last visit. Each visit adds 1 to the rank. It also keeps the same two numbers per app, keyed by the lower-case process name. + +A folder's score is its rank times a factor that depends on how long ago you last used it: + +| Last used | Factor | +| --- | --- | +| Less than 1 hour ago | 4 | +| Less than 1 day ago | 2 | +| Less than 7 days ago | 0.5 | +| Longer ago | 0.25 | + +So that the **Recent** list can favor the app you're in, the sort key is: + +```text +score(folder) + 3 * score(folder's use in this app) +``` + +where each `score` is rank times the factor for that last-used time. The factor `3` is `AppWeight`. A few uses in the current app outweigh a modest lead elsewhere, but a far more popular folder still wins. Ties go to the most recent visit. With no app (the tray icon), the second term is zero. + +The rest of the rules: + +- A repeat visit within 2 seconds is ignored. The folder and each app have their own 2-second check. +- When the sum of all ranks goes above 9000, every rank is multiplied by 0.9, and entries below 1 are dropped. The folder being recorded is never dropped. +- At most 1000 folders are kept. If there are more, the lowest scores go first. +- At most 10 apps are kept for each folder. +- Folders that match a pattern in `historyExclude` are neither recorded nor returned. +- The menu asks for `recentCount + 1` folders, since one may be the current folder, which is left out. + +History is saved in `history.json`, written to a temporary file that then replaces the old one. The tray app saves it every 60 seconds if it changed, when Settings asks for it, and when it exits. + +## The menu + +The menu has three stages. + +1. **Model.** `MenuComposer` (`src/Folderite/Menu/MenuComposer.cs`) turns config and context into a tree of items, submenus, headers and separators (`MenuModel.cs`). It touches neither Win32 nor the disk, which is why unit tests can cover it. +2. **Native popup.** `MenuBuilder` creates a Win32 popup menu from the tree and shows it with `TrackPopupMenuEx`, which returns the chosen command. The items are owner-drawn: the builder answers `WM_MEASUREITEM` and `WM_DRAWITEM` itself, and `MenuTheme` supplies colors, fonts and sizes for the DPI at the click point, in the light or dark app theme, with the accent color for headers. It uses Segoe Fluent Icons (or Segoe MDL2 Assets) for the submenu arrow. `DarkMode` makes the popup frame follow the theme with two undocumented `uxtheme` calls. `IconCache` supplies shell icons. +3. **Lazy submenus.** A favorite's subfolder list and the Drives list aren't read until you open them (`WM_INITMENUPOPUP`), so a network folder that's gone can't stall the menu when it opens. + +Other details: + +- The menu is modal. The action runs after it is destroyed. +- Because the hook swallowed the click, Windows won't let the hidden window take the foreground, and a popup whose owner isn't in front doesn't close when you click outside it. So `MenuBuilder` attaches to the foreground thread's input for that one call. +- Menu animation is turned off while the menu is open, because owner-drawn submenus glitch when they fade. Only the session is changed, nothing is written. +- Triggering again while a menu is open closes it. +- Accelerator keys (`&`) are handled through `WM_MENUCHAR`, because the items are owner-drawn. + +## The Settings process + +**Settings** starts `Folderite.exe --settings`, which runs a WPF app (`src/Folderite/Settings/SettingsApp.cs`) in its own process. It has its own mutex, `Local\Folderite.Settings`, and the tray app brings an existing Settings window forward by finding its title, `Folderite Settings`. A separate process keeps WPF's memory and startup cost out of the tray app, which runs all the time. + +The two processes share two files and a small command channel: + +- **`config.json`.** Settings has its own `ConfigStore`, edits the settings and writes the file. The tray app notices the change (see below). +- **`history.json`.** The tray app owns it while it runs. Settings asks the tray app to save (`flush-history`) before it reads the file, and to remove or clear entries (`remove-history`, `clear-history`). If the tray app isn't running, Settings edits the file itself. +- **Commands.** `src/Folderite/Interop/Ipc.cs` sends text commands to the tray app with `WM_COPYDATA`. The receiver is the hidden window, found by its class `FolderiteMessageWindow`. + +The message carries a fixed marker (`FLDR`) in `dwData`, and UTF-16 text of the form `command` or `command\nargument`, up to 64 KB. The sender uses `SendMessageTimeout` with a one-second limit that gives up on a hung receiver. The receiver ignores messages without the marker. The commands: + +| Command | Does | +| --- | --- | +| `flush-history` | Saves history if there are unsaved changes. | +| `remove-history` | Removes one folder from history. | +| `clear-history` | Clears history. | +| `get-paused`, `set-paused` | Reads or sets Pause. | +| `get-hotkey-status` | Tells whether the keyboard shortcut is registered. | +| `exit` | Quits the tray app. Used when you move the config folder. | + +There is no `ChangeWindowMessageFilterEx` call, since both processes run at the same integrity level. + +## Config reload + +`ConfigStore` (`src/Folderite/Config/ConfigStore.cs`) loads `config.json` (comments and trailing commas allowed) and watches it with a `FileSystemWatcher`. Both the tray app and the Settings window use it. When the file changes: + +1. A 300 ms timer restarts on each event, so an editor's several writes count once. +2. Changes within one second after the app's own save are ignored, so Settings doesn't reload what it wrote itself. +3. The file is read with sharing allowed. If another program has it locked, Folderite tries up to three times, 100 ms apart. +4. If it parses, the new config replaces the old one and the `Changed` event runs on the main thread. If it doesn't, the old config stays in use and the error is kept. The tray menu's **Reload** item reports it with a balloon. + +`App.ApplyConfig` then updates the parts that are cached: the disabled-apps list, the history exclusions, the icon cache and the keyboard shortcut. Everything else, such as favorites, actions, recent count and the section switches, is read from the current config each time the menu opens, so it needs no work. + +Saves write to a temporary file and move it over `config.json`, so a half-written file is never seen. If the keyboard shortcut text changed, it is unregistered and registered again, and a failure shows a balloon once. diff --git a/docs/explorer-dark.gif b/docs/images/explorer-dark.gif similarity index 100% rename from docs/explorer-dark.gif rename to docs/images/explorer-dark.gif diff --git a/docs/explorer-light.gif b/docs/images/explorer-light.gif similarity index 100% rename from docs/explorer-light.gif rename to docs/images/explorer-light.gif diff --git a/docs/explorer-still-dark.png b/docs/images/explorer-still-dark.png similarity index 100% rename from docs/explorer-still-dark.png rename to docs/images/explorer-still-dark.png diff --git a/docs/explorer-still-light.png b/docs/images/explorer-still-light.png similarity index 100% rename from docs/explorer-still-light.png rename to docs/images/explorer-still-light.png diff --git a/docs/menu-dark.png b/docs/images/menu-dark.png similarity index 100% rename from docs/menu-dark.png rename to docs/images/menu-dark.png diff --git a/docs/menu-light.png b/docs/images/menu-light.png similarity index 100% rename from docs/menu-light.png rename to docs/images/menu-light.png diff --git a/docs/save-dialog-dark.gif b/docs/images/save-dialog-dark.gif similarity index 100% rename from docs/save-dialog-dark.gif rename to docs/images/save-dialog-dark.gif diff --git a/docs/save-dialog-light.gif b/docs/images/save-dialog-light.gif similarity index 100% rename from docs/save-dialog-light.gif rename to docs/images/save-dialog-light.gif diff --git a/docs/save-dialog-still-dark.png b/docs/images/save-dialog-still-dark.png similarity index 100% rename from docs/save-dialog-still-dark.png rename to docs/images/save-dialog-still-dark.png diff --git a/docs/save-dialog-still-light.png b/docs/images/save-dialog-still-light.png similarity index 100% rename from docs/save-dialog-still-light.png rename to docs/images/save-dialog-still-light.png diff --git a/docs/settings-dark.png b/docs/images/settings-dark.png similarity index 100% rename from docs/settings-dark.png rename to docs/images/settings-dark.png diff --git a/docs/settings-light.png b/docs/images/settings-light.png similarity index 100% rename from docs/settings-light.png rename to docs/images/settings-light.png diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..307fd13 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,28 @@ +# Folderite + +Folderite is a folder menu for Windows. Middle-click in Explorer or in an Open or Save dialog, and a menu of your favorites, the folders open in other windows, your recent folders and the folder on the clipboard appears at the cursor. Pick one and that window goes there. It is free, open source (MIT), and runs in the tray. + +![The Folderite menu over an Explorer window: the current folder, favorites, open windows, recent folders and the clipboard folder](images/menu-light.png#only-light) +![The Folderite menu over an Explorer window: the current folder, favorites, open windows, recent folders and the clipboard folder](images/menu-dark.png#only-dark) + +## Where to start + +New to Folderite? Read these two in order: + +1. [Install and uninstall](install.md): requirements, the download, upgrading and removing it. +2. [Getting started](getting-started.md): the first middle-click and your first favorite. + +Then the pages on using it: + +- [The folder menu](menu.md): every section of the menu, the tray icon and the keyboard shortcut. +- [Settings](settings.md): the Settings window, page by page. +- [Open and Save dialogs](dialogs.md): moving a dialog without losing the typed file name, and choosing the folder a dialog starts in. +- [Supported apps](apps.md): Explorer, Directory Opus, Total Commander, XYplorer, Blender and the desktop, and the limits of each. +- [The config file](config.md): every key in `config.json`. +- [Files and logs](files.md): where everything is stored, moving it, and running portable. + +If something does not work, start with [Troubleshooting](troubleshooting.md). [How it works](how-it-works.md) explains what happens between the click and the folder change, and [Build from source](building.md) is for working on Folderite itself. + +## Feedback + +Bug reports and ideas are welcome at [github.com/combwizard/Folderite/issues](https://github.com/combwizard/Folderite/issues). diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000..4d23702 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,83 @@ +# Install and uninstall + +Folderite has no installer. You unzip a folder, run `Folderite.exe`, and it sits in the notification area. Uninstalling means deleting that folder, plus a few leftovers listed [below](#uninstall). + +## Requirements + +- Windows 10 version 1903 or later, or Windows 11. +- A 64-bit PC. The release is built for `win-x64` only. +- The [.NET 10 Desktop Runtime (x64)](https://dotnet.microsoft.com/download/dotnet/10.0). Folderite is built as a framework-dependent app, so the release does not carry its own copy of .NET. Other .NET 10 apps share the same runtime. + +You do not need administrator rights. Folderite runs with the same rights as the program that starts it. By default it writes only to your own profile and the current-user part of the registry. + +## Install + +1. Install the .NET 10 Desktop Runtime (x64) if you do not have it. +2. Download `Folderite--win-x64.zip` from the [latest release](https://github.com/combwizard/Folderite/releases/latest). +3. Unzip it into a folder you will keep, for example `C:\Tools\Folderite`. The zip has no folder inside it, so extract it into a folder of its own. +4. Run `Folderite.exe`. + +Folderite opens no window. Look for its icon in the notification area at the right end of the taskbar. If you do not see it, open the overflow with the small arrow, and drag the icon out to keep it visible. Hover the icon to see the version, for example "Folderite 0.3.0". + +Keep the folder where it is. Folderite runs from there, and [Start with Windows](#start-with-windows) remembers that location. + +Running `Folderite.exe` a second time does nothing, because only one copy runs at a time. + +### Windows says "Windows protected your PC" + +The exe is not code-signed, so SmartScreen may warn about it the first time it runs. Click **More info**, then **Run anyway**. + +The release zip is built from the public source by GitHub Actions, and you can always [build it yourself](building.md). + +## First run + +The first start creates the config folder `%APPDATA%\Folderite` and a `config.json` in it with a starter setup: + +- four favorites: **Home**, **Desktop**, **Downloads** and **Documents**, +- one action, **Open terminal here**, which runs `wt.exe` (Windows Terminal), +- the Open windows and Clipboard sections on, and the Drives section off. + +Folderite also keeps your folder history in `history.json` there, and writes `folderite.log` next to them when something goes wrong. See [Files and logs](files.md) for where these live and how to move them. + +Go on to [Getting started](getting-started.md) for your first middle-click. + +## Start with Windows + +To have Folderite start when you sign in, do either of these: + +- Right-click the tray icon and choose **Start with Windows: Off**. The entry then reads **Start with Windows: On**. +- Open [Settings](settings.md) and turn on **Start with Windows** under **General**. + +Folderite writes a value named `Folderite` under `HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run`. Its data is the full, quoted path of `Folderite.exe`. It is per user, so it needs no administrator rights. Turning the option off deletes the value. + +If you move or rename the Folderite folder later, the value still points at the old path, and the tray menu still shows **On** because the value exists. Choose it to turn it **Off**, then run `Folderite.exe` from its new place and turn it **On** again. + +## Upgrade + +1. Right-click the tray icon and choose **Exit**. Close the Settings window too if it is open. Settings runs from the same `Folderite.exe`, and Windows will not let you overwrite a running file. +2. Unzip the new release over the old folder and replace the files. +3. Run `Folderite.exe` again. + +Your settings are kept. They live in `%APPDATA%\Folderite`, outside the program folder. If you made Folderite [portable](files.md), `config.json` sits next to the exe, and the zip does not contain that file, so unzipping does not overwrite it. + +Folderite does not check for updates. Watch the [releases page](https://github.com/combwizard/Folderite/releases) if you want to hear about new versions. The current version is shown in the tray icon tooltip and under **Settings**, **General**. + +If **Start with Windows** was on and the folder stayed the same, it keeps working after the upgrade. + +## Uninstall + +1. If **Start with Windows** is on, turn it off first: right-click the tray icon and choose **Start with Windows: On**. This removes the registry value. +2. Right-click the tray icon and choose **Exit**. Close the Settings window if it is open. +3. Delete the Folderite folder. + +That removes the program. Folderite leaves these behind, and each is safe to delete: + +| What | Where | When it exists | +| --- | --- | --- | +| Settings, history and log | `%APPDATA%\Folderite` (`config.json`, `history.json`, `folderite.log`), or the folder you moved them to | Always, unless Folderite was portable, in which case they were in the program folder and are already gone | +| Remembered config location | `HKEY_CURRENT_USER\Software\Folderite`, value `ConfigDirectory` | Only if you used **Change location…** to pick another folder | +| Start with Windows entry | `HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run`, value `Folderite` | Only if you left **Start with Windows** on | + +Delete the registry values with Registry Editor if you want no trace. Folderite only reads the Windows theme setting under `Software\Microsoft\Windows\CurrentVersion\Themes\Personalize`. It never changes it. + +Folderite adds no shortcuts, services, scheduled tasks or machine-wide settings. diff --git a/docs/javascripts/lightbox.js b/docs/javascripts/lightbox.js new file mode 100644 index 0000000..2f0d6dd --- /dev/null +++ b/docs/javascripts/lightbox.js @@ -0,0 +1,133 @@ +/* Click a picture in the docs to see it larger. A click anywhere, the close button or Esc closes it. + On a phone it shows the picture at its own size and you pan across it, with the close button staying put. Only pictures that would show bigger are offered: one already shown at its full size is left alone, and so is + one inside a link (the link wins). A picture can opt out with {data-no-zoom} (attr_list). The page keeps its + scroll position, and focus goes back to the picture. A hidden theme variant (#only-dark in light mode) is never + offered, and the check is repeated when the system theme flips. Styles are in stylesheets/extra.css (fl-lightbox). */ +(function () { + "use strict"; + + var box = null, opener = null; + + function inPage(img) { + return !!img.closest(".md-content .md-typeset") && !img.closest("a, .fl-lightbox") && !img.hasAttribute("data-no-zoom"); + } + + /* Whether the picture is shown smaller than it is, so enlarging it shows something. A hidden one (width 0) never is. */ + function worthIt(img) { + if (!img.clientWidth) return false; + return img.naturalWidth > img.clientWidth + 16 || img.naturalHeight > img.clientHeight + 16; + } + + function unmark(img) { + if (!img.classList.contains("fl-zoomable")) return; + img.classList.remove("fl-zoomable"); + img.removeAttribute("tabindex"); + img.removeAttribute("role"); + img.removeAttribute("aria-haspopup"); + if (img.title === "Click to enlarge") img.removeAttribute("title"); + } + + function mark(img) { + if (!inPage(img) || !worthIt(img)) { unmark(img); return; } + img.classList.add("fl-zoomable"); + img.tabIndex = 0; + img.setAttribute("role", "button"); + img.setAttribute("aria-haspopup", "dialog"); + img.title = "Click to enlarge"; + } + + function markAll() { + document.querySelectorAll(".md-content .md-typeset img").forEach(function (img) { + if (img.complete) mark(img); + else img.addEventListener("load", function () { mark(img); }, { once: true }); + }); + } + + function close() { + if (!box) return; + var gone = box; + box = null; + document.documentElement.classList.remove("fl-lightbox-open"); + gone.classList.remove("on"); + setTimeout(function () { gone.remove(); }, 200); + if (opener && document.contains(opener)) opener.focus({ preventScroll: true }); + opener = null; + } + + function open(img) { + close(); + opener = img; + var alt = img.getAttribute("alt") || ""; + box = document.createElement("div"); + box.className = "fl-lightbox"; + box.setAttribute("role", "dialog"); + box.setAttribute("aria-modal", "true"); + box.setAttribute("aria-label", alt || "Enlarged picture"); + + var fig = document.createElement("figure"); + var big = document.createElement("img"); + big.src = img.currentSrc || img.src; + big.alt = alt; + fig.appendChild(big); + if (alt) { + var cap = document.createElement("figcaption"); + cap.textContent = alt; + fig.appendChild(cap); + } + + var x = document.createElement("button"); + x.type = "button"; + x.className = "fl-lightbox-x"; + x.setAttribute("aria-label", "Close"); + x.innerHTML = ''; + + /* The picture scrolls in its own layer. The close button stays outside it, so panning never moves it. */ + var pan = document.createElement("div"); + pan.className = "fl-lightbox-pan"; + pan.appendChild(fig); + + box.append(pan, x); + box.addEventListener("click", close); + document.body.appendChild(box); + document.documentElement.classList.add("fl-lightbox-open"); + void box.offsetWidth; // lay it out at opacity 0 first, so it fades in + box.classList.add("on"); + x.focus({ preventScroll: true }); + } + + document.addEventListener("click", function (e) { + var img = e.target; + if (img && img.classList && img.classList.contains("fl-zoomable") && e.button === 0) open(img); + }); + + document.addEventListener("keydown", function (e) { + if (box) { + if (e.key === "Escape") { e.preventDefault(); close(); } + else if (e.key === "Tab") e.preventDefault(); // the close button is the only stop + return; + } + var img = e.target; + if ((e.key === "Enter" || e.key === " ") && img && img.classList && img.classList.contains("fl-zoomable")) { + e.preventDefault(); + open(img); + } + }); + + /* The system theme flipped: the other picture variant is now the visible one, so close an open picture and mark again. + (The palette script listens first, so the attribute has already changed by the time this runs.) */ + if (window.matchMedia) { + var dark = matchMedia("(prefers-color-scheme: dark)"); + var onTheme = function () { close(); markAll(); }; + if (dark.addEventListener) dark.addEventListener("change", onTheme); + else if (dark.addListener) dark.addListener(onTheme); + } + + /* Material's instant navigation swaps the page without a reload: mark the new page's pictures, drop an open one. */ + if (window.document$ && window.document$.subscribe) { + window.document$.subscribe(function () { close(); markAll(); }); + } else if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", markAll); + } else { + markAll(); + } +})(); diff --git a/docs/menu.md b/docs/menu.md new file mode 100644 index 0000000..c2c19df --- /dev/null +++ b/docs/menu.md @@ -0,0 +1,188 @@ +# The folder menu + +The folder menu is a native popup menu that Folderite opens at the pointer. It lists places to go. When you pick one, the window you opened the menu over goes there. + +![The Folderite menu over an Explorer window: the current folder, favorites, open windows, recent folders and the clipboard folder](images/menu-light.png#only-light) +![The Folderite menu over an Explorer window: the current folder, favorites, open windows, recent folders and the clipboard folder](images/menu-dark.png#only-dark) + +## Ways to open it + +| How | Opens the menu for | Current folder shown | Picking a folder | +| --- | --- | --- | --- | +| Middle-click in a supported window | The window you clicked | Yes, when Folderite can read it | Navigates that window | +| [Keyboard shortcut](#keyboard-shortcut) | The window in front | Yes, when that window is supported | Navigates that window, or opens a new Explorer window if it is not supported | +| Left-click the [tray icon](#the-tray-icon) | No window | No | Opens a new Explorer window | + +Middle-click needs the pointer over a [supported window](apps.md), and Folderite swallows the click, so the app under the pointer never sees it. The menu is built at that moment from your [settings](settings.md), so it always reflects the current favorites, windows, history and clipboard. + +## The sections, top to bottom + +Sections are separated by lines. A section that has nothing to show leaves out its line too, so the menu never has a doubled or a trailing line. + +| Section | Shown when | +| --- | --- | +| [Current folder](#current-folder) | Folderite can read the folder of the window | +| [Favorites](#favorites) | You have favorites | +| [Open windows](#open-windows) | **Show open windows** is on, and another window is open on a folder | +| [Recent](#recent-folders) | **Recent folders** is above 0 and there is a folder to list | +| [Drives](#drives) | **Show drives** is on (off by default) | +| [Clipboard](#clipboard-folder) | **Show clipboard folder** is on, and the clipboard holds a folder path that is not the current folder | +| [Settings… and Reload](#settings-and-reload) | Always | + +Turn the optional sections on and off, and set the number of recent folders, on the **Menu** page of [Settings](settings.md). The matching config keys are in the [config reference](config.md). + +### Current folder + +The first item is named after the folder you are in, for example "aurora-web", and opens a submenu. It is a submenu rather than a folder to click, because you are already there. It holds: + +- **Copy path**: puts the full path on the clipboard as text. +- Your **actions**, in the order you set them. The starter setup has **Open terminal here**. An action runs its command with the folder as the working directory, and `{path}` in its arguments is replaced by the folder. See [Actions in Settings](settings.md). +- **Add to favorites**, after a line. It adds the folder to the end of your favorites and saves. It is left out when the folder is already a favorite, wherever it sits in your groups. + +This item is missing when Folderite cannot tell which folder the window shows: + +- the menu was opened from the tray icon, or with the shortcut over a window that is not supported, +- Explorer is on a virtual place such as **This PC**, **Home** or a search result, +- the window is Blender's file browser, which does not say which folder it shows. + +On the desktop, the current folder is your Desktop folder. + +### Favorites + +Your favorites appear in the order you set. Each one is a folder you click to go to. Environment variables in a path, such as `%USERPROFILE%`, are expanded. A favorite with no name shows the folder's name. A favorite with no folder and no items is skipped. + +A single `&` in a name marks the shortcut key, as in [Accelerator keys](#accelerator-keys). + +#### Groups and separators + +A favorite with items under it becomes a submenu, for example **Work** with **Specs**, **Reports** and **Design** inside. Groups can nest. An empty group shows "(empty)" when opened. + +A favorite named `-` becomes a line. In Settings, use the separator button for this. + +#### Favorites with subfolders + +Turn on **Show subfolders in a submenu** for a favorite and it becomes a submenu of its subfolders: + +- The folder itself is the first item, in bold, so you can still go to it. A line follows. +- The subfolders are listed after that, sorted by name in Explorer's order ("file2" before "file10"), or **Newest first** by the time each folder was last changed. +- Hidden and system folders are left out. +- A list longer than 100 folders is cut at 100, and a dimmed line says "Showing the first 100 folders". + +Folderite reads the disk only when you open that submenu. If a network folder or a drive takes longer than about a second and a half to answer, the submenu shows only the folder itself instead of freezing the menu. + +### Open windows + +Under the heading **Open windows**, Folderite lists the folders open right now in: + +- Windows Explorer, including every tab, +- Directory Opus, +- Total Commander, +- XYplorer. + +It shows up to 10, leaves out the folder you are in, and lists each folder once even if it is open in several windows. The dimmed text on the right is the parent folder. + +File dialogs, Blender and the desktop add nothing to this list. + +### Recent folders + +The **Recent** submenu lists the folders you use most, best first. It shows up to **Recent folders** entries (15 by default, 0 to 50 in Settings). It leaves out the folder you are in. A folder can appear in both **Open windows** and **Recent**. With 0, the section is hidden. + +Folderite learns folders without any action from you. It notes a folder when you move to it in Explorer, a file dialog, Directory Opus, Total Commander or XYplorer, when you bring such a window to the front, when you open the menu over it, and when you pick it from the menu. A repeat within 2 seconds counts once. Folders that match a pattern under **Exclusions**, **Don't remember these folders**, are never recorded. Use **History** in Settings to look at the list, remove a folder, or clear it. + +#### How recent folders are ranked + +Folderite ranks folders by frecency, a mix of how often and how recently you used each one, in the style of zoxide. + +Each visit adds 1 to a folder's count. The count is then multiplied by how long ago the folder was last used: + +| Last used | Count multiplied by | +| --- | --- | +| Within the last hour | 4 | +| Within the last day | 2 | +| Within the last week | 0.5 | +| Longer ago | 0.25 | + +The folder with the highest result comes first. A tie goes to the folder used most recently. + +**The boost for the current app.** Folderite also counts the visits made in each app, using the program name (`explorer`, `notepad`, and so on). When you open the menu over an app, each folder's score gets three times its score in that app, worked out the same way. A few visits in that app outweigh a modest lead elsewhere, so over Notepad's Save dialog the folders you use with Notepad rise. A folder you use far more overall still wins. Opening the menu from the tray icon has no app, so there is no boost. + +Folderite keeps at most 1,000 folders. When the total of all counts grows past 9,000, every count shrinks by 10 percent and folders that fall below 1 are dropped. History is saved to `history.json` every minute and when Folderite exits. See [Files and logs](files.md). + +### Drives + +With **Show drives** on, a **Drives** submenu appears after **Recent**. It lists each drive as "Local Disk (C:)" or by its volume label, with the free space on the right, for example "12.3 GB free of 476 GB". It is read when you open the submenu, and a drive that does not answer in time is listed without sizes. + +### Clipboard folder + +The **Clipboard** item appears when the clipboard holds text that is one full folder path, such as `C:\Projects\notes-cli\docs`. Surrounding quotes are ignored. The path must exist, except for network paths starting with `\\`, which are accepted as they are. Text with several lines, text over 1,024 characters, and relative paths do not count. The path shows dimmed on the right. The item is hidden when the clipboard folder is the folder you are already in. + +### Settings and Reload + +The last two items are always there. + +- **Settings…** opens the [Settings window](settings.md), or brings it to the front if it is open. +- **Reload** reads `config.json` again. Folderite also reloads it by itself when the file is saved, so you need this only if something went wrong. If the file has an error, Folderite shows a notification with the message. + +## Accelerator keys + +Every named item has an accelerator key. While the menu is open, press the letter to pick the item or open the submenu. + +- In a name you write yourself, put `&` before the letter: `&Projects` is picked with **P**. Type `&&` for a plain ampersand. +- When no item has the letter marked, items whose name starts with that letter match. This is how you reach recent folders, open windows, subfolders and drives, which carry no `&`. +- The built-in items use these letters: **C** for **Copy path**, **A** for **Add to favorites**, **R** for **Recent**, **V** for **Drives**, **L** for **Clipboard**, **S** for **Settings…**, **D** for **Reload**. The starter favorites use **H**, **D**, **W** and **O**. The starter action uses **T**. +- If one letter matches only one item in the open menu, it is picked at once. If it matches several, the first press highlights the next match after the current one, and each further press moves to the following match. Press **Enter** to pick the highlighted item. + +The arrow keys, **Enter** and **Esc** work as in any Windows menu. + +## What picking a folder does + +- **In a supported window**, Folderite brings the window to the front and sends it to the folder. In Explorer with tabs, it changes the tab you are in. In a file dialog, it keeps the file name you typed. In a folder picker, it navigates the picker instead of accepting the folder. For each app, see [Supported apps](apps.md). +- **From the tray icon, or with the shortcut over an unsupported window**, it opens the folder in a new Explorer window. The same happens on the desktop. +- Folderite records the folder in your history, with the app it was used in. +- If the window cannot be sent there, Folderite shows a notification saying it could not open the folder in that app. + +Pressing **Esc**, or clicking outside the menu, closes it and does nothing. + +## Where middle-click does not open the menu + +Middle-click acts normally in all of these. + +- **Windows Folderite does not support.** In a browser, an editor or any other app, middle-click works as it always has. +- **The tab row in Windows 11 Explorer.** Middle-clicking a tab still closes it. The exemption covers the title-bar row. Where Explorer has no such row, as on Windows 10, the whole window counts. +- **Apps you excluded.** Add a program name under **Exclusions**, **Don't open the menu in these apps** (`disabledProcesses`), for example `notepad`. The name is that of the program that owns the window. A file dialog belongs to the app that opened it, so excluding `notepad` also covers Notepad's Open and Save dialogs. +- **While paused.** Choose **Pause** in the tray menu to turn off middle-click and the keyboard shortcut until you choose **Resume**. Pause is not saved: Folderite starts running again after a restart. Left-clicking the tray icon still opens the menu while paused. +- **The desktop, when you turned it off.** The desktop is on by default. **Work on the desktop** on the **Menu** page controls it. +- **Blender windows other than its file browser.** Only the "Blender File View" window counts. For the other apps, see [Supported apps](apps.md). + +An excluded app also changes the shortcut: with an excluded app in front, the shortcut opens the menu as it does for an unsupported window, so picking a folder opens a new Explorer window. + +## Keyboard shortcut + +The shortcut is off until you set one. Open **Settings**, **General**, click the **Keyboard shortcut** box and press the keys you want, for example **Win**+**Shift**+**F**. Press **Backspace** or **Delete** in the box, or click **Clear**, to remove it. The config key is `hotkey`, written like `Win+Shift+F`. + +- It needs **Ctrl**, **Alt**, **Shift** or **Win**. Function keys (F1 to F24) work alone. +- It opens the menu for the window in front, using the control that has the keyboard focus. Total Commander and Directory Opus use that to pick the left or right panel. The menu appears at the pointer when the pointer is over that window, and at the center of the window otherwise. +- The tab-row exemption does not apply, because no click is involved. +- Press it again while the menu is open to close it. +- It does nothing while paused. +- With no supported window in front, the menu opens at the pointer with no current folder, and picking a folder opens a new Explorer window. +- If another app already uses the shortcut, Folderite shows a notification, and Settings shows "This shortcut is already used by another app. Pick another one." under the box. + +## The tray icon + +Folderite lives in the notification area. The tooltip reads "Folderite" followed by the version. + +- **Left-click** opens the folder menu, with no current folder. Picking a folder opens a new Explorer window. This works while paused and does not depend on any window. +- **Right-click** opens the tray menu. + +| Tray menu item | Key | What it does | +| --- | --- | --- | +| **Pause** or **Resume** | **P** / **R** | Turns middle-click and the shortcut off, or back on. Not saved across restarts. | +| **Start with Windows: On** or **Off** | **O** | Turns starting at sign-in on or off. See [Install](install.md#start-with-windows). | +| **Settings…** | **S** | Opens the [Settings window](settings.md). | +| **Edit config…** | **E** | Opens `config.json` in the program Windows uses for `.json` files, or in Notepad if there is none. | +| **Open config folder** | **F** | Opens the config folder in a new Explorer window. | +| **Reload** | **D** | Reads `config.json` again. | +| **Exit** | **X** | Quits Folderite. | + +Folderite also uses the tray icon for notifications: a config error, a keyboard shortcut that did not register, an action that failed to start, and a folder it could not open. diff --git a/docs/settings.md b/docs/settings.md new file mode 100644 index 0000000..0225856 --- /dev/null +++ b/docs/settings.md @@ -0,0 +1,254 @@ +# Settings + +The Settings window edits `config.json` for you. This page goes through it page by page. For the file itself, see [Config file](config.md). + +## Open Settings + +Any of these opens the window: + +- Right-click the tray icon and choose **Settings…**. +- Open the folder menu and choose **Settings…** at the bottom. +- Run `Folderite.exe --settings`. + +Settings is a separate process from the tray app. Only one Settings window opens at a time: choosing **Settings…** while it is open brings it to the front, and a second `--settings` does nothing. + +The window has a list of pages on the left. In order, they are **Favorites**, **Actions**, **Menu**, **Dialogs**, **Exclusions**, **History** and **General**. The page title and a **Preview menu** button sit above each page, and a status line sits below it. + +## How saving works + +There is no Save button. Settings writes `config.json` about 0.4 seconds after your last change, and once more when you close the window. The status line says **Saved** when it has written the file. The tray app notices the new file and reloads it, so a change takes effect without restarting Folderite. + +Two things to know: + +- Settings rewrites the whole file each time, so `//` comments in `config.json` are not kept. +- If you edit `config.json` in a text editor while Settings is open and have no unsaved change in Settings, the window reloads the file and says **Reloaded: config.json changed on disk**. + +The **History** page and the **Start with Windows** and **Pause Folderite** switches on **General** are not part of `config.json`. They take effect as soon as you use them. + +### When config.json has an error + +If `config.json` can't be read (a missing bracket, say), a red banner appears under the page title: **config.json has an error, so changes here are not saved.** Below it is the parser's message. Settings turns autosave off so it doesn't overwrite a file you are in the middle of fixing. The status line says **Autosave is off: config.json has errors**, and any edit you make shows **Not saved: fix config.json or reload**. + +The banner has two buttons: + +| Button | What it does | +| --- | --- | +| **Open file** | Opens `config.json` in your default editor for `.json` files, or in Notepad if there is none. | +| **Reload** | Reads the file again. If it is valid now, the banner goes away and autosave comes back. Edits made in Settings while autosave was off are discarded. | + +### Preview menu + +**Preview menu**, at the top right of every page, pops up the real folder menu built from the settings as they are now, even before they are saved. The folder it pretends to be over is your user profile folder. Picking an item does nothing; the status line says what would have happened, for example **Preview: would open D:\Projects**. + +### Status line + +The line under the page shows the last thing that happened: + +| Text | Meaning | +| --- | --- | +| **Saved** | `config.json` was written. | +| **Not saved: …** | The write failed (the reason follows), or autosave is off because of a config error. | +| **Reloaded: config.json changed on disk** | The file was edited outside Settings and Settings picked up the change. | +| **Autosave is off: config.json has errors** | See the banner above. | +| **Preview: …** | What a pick in the previewed menu would do. | +| **Moving settings…** | [Change location](files.md) is working. | + +## Favorites + +![The Settings window on the Favorites page, with the Work group open and the folder Specs selected](images/settings-light.png#only-light) +![The Settings window on the Favorites page, with the Work group open and the folder Specs selected](images/settings-dark.png#only-dark) + +Favorites are the folders and submenus at the top of the folder menu. The page has the tree on the left and the details of the selected item on the right. A new `config.json` starts with Home, Desktop, Downloads and Documents. + +Each row in the tree shows an icon, the name, its shortcut key (the letter after the name), and for folders the path. A warning triangle after a folder means it does not exist. The check only applies to paths on a drive letter, and the favorite stays in the menu either way. + +### Adding + +The toolbar above the tree has these buttons: + +| Button | What it does | +| --- | --- | +| **Add folder** | Opens a folder picker that accepts several folders at once. Each becomes a favorite named after its folder. | +| Add a group (submenu) | Adds a group named **New group**. | +| Add a separator | Adds a line between menu items. | +| Move up, Move down | Moves the selected row one place among its siblings. | +| Remove (Del) | Removes the selected row. There is no confirmation. | + +Where the new row goes depends on the selection. With a group selected, it goes inside the group at the end. With any other row selected, it goes right after that row, at the same level. With nothing selected, it goes at the end of the top level. The row you add is selected, ready to edit. + +In the tree, **Del** removes the selected row and **Alt+Up** and **Alt+Down** move it. + +### Reordering and nesting + +Drag a row to move it. A line shows where it will land, or an outline appears around a group when you are dropping into it: + +- On a folder or separator, the top half of the row means before it and the bottom half means after it. +- On a group, the top 30 percent means before the group, the bottom 30 percent means after it, and the middle means inside it, at the end. +- Dropping below the last row puts the item at the end of the top level. + +You can't drop a group into itself or into one of its own subgroups. Groups can nest as deeply as you like. + +### Dropping folders from Explorer + +Drag one or more folders from an Explorer window onto the tree. Each one becomes a favorite, placed where you drop it, in the order you dragged them. Files in the drop are ignored. With nothing in the tree yet, the empty list says **No favorites yet. Add a folder, or drop one here.** + +### Folder details + +Select a folder to edit it: + +| Field | What it does | +| --- | --- | +| **Name** | The text in the menu. Leave it empty to show the folder's own name. See [Shortcut keys](#shortcut-keys). | +| **Folder** | The path. The button next to it opens a folder picker. Environment variables such as `%USERPROFILE%` are allowed. | +| **Show subfolders in a submenu** | Off by default. When on, the favorite becomes a submenu listing the folder's subfolders. | +| **Sort by name** / **Newest first** | Shown when subfolders are on. **Sort by name** is the default and uses Explorer's order, so `file2` comes before `file10`. **Newest first** orders by last modified time. | + +With subfolders on, the first item in the submenu is the folder itself, in bold, followed by a line and then the subfolders. The list is read when you open the submenu. Hidden and system folders are left out, and a long folder shows only the first 100 subfolders, with a note saying so. A submenu row can't be clicked itself, which is why the folder is repeated as the first item. + +The file stores this as `"showSubfolders": true` and `"sortSubfolders": "newest"`. By default neither is written. + +### Groups and separators + +A group has only a **Name**. In the menu it is a submenu holding the group's rows. A separator has nothing to set. In `config.json` it is an entry with the name `-`. + +### Shortcut keys + +Put `&` before a letter in a name to make it the shortcut key for that item. With the menu open, pressing the letter picks the item, or opens the submenu if it is a group. Write `&&` for a literal `&`. For example, `&Projects` is picked with **P**, and `R&&D` shows as `R&D`. The details panel shows **Shortcut key:** with the letter it found. + +If two items in the same menu use the same letter, pressing it moves between them. If no item marks the letter you press, Folderite uses the items whose name starts with it. A new folder you add gets no `&`, so the first-letter rule applies until you add one. + +## Actions + +Actions are commands that run on the folder the menu was opened over. They appear in the submenu at the top of the menu, under the name of the current folder, after **Copy path**. Actions need a current folder, so they are not there in the menu you get from the tray icon. + +A new `config.json` has one action, **Open &terminal here**, which runs `wt.exe` with `-d "{path}"`. + +The toolbar has **Add action**, Move up, Move down and Remove. Unlike Favorites, Del does not remove an action: use the Remove button. A new action is named **New action**, has no command, and has the arguments `"{path}"`. + +| Field | What it does | +| --- | --- | +| **Name** | The text in the menu. `&` marks the shortcut key, as for [favorites](#shortcut-keys). | +| **Command** | The program or script to run. The button opens a file picker titled **Choose a program** that lists `.exe`, `.cmd`, `.bat` and `.ps1` files, or all files. Environment variables such as `%LOCALAPPDATA%` are expanded. | +| **Arguments** | The command line passed to the program. | + +### Placeholder + +`{path}` is the only placeholder. Folderite replaces every `{path}` in **Arguments** with the full path of the current folder. It adds no quotes of its own, so put them around it: `-d "{path}"`. Placeholders are not expanded in **Command**, and `%` variables are not expanded in **Arguments**. + +The program starts with the current folder as its working directory. If it can't start, a tray notification says ` failed:` followed by the reason. + +Example actions: + +| Name | Command | Arguments | +| --- | --- | --- | +| `Open in &VS Code` | `code` | `"{path}"` | +| `Open &terminal here` | `wt.exe` | `-d "{path}"` | + +## Menu + +Which sections the folder menu has. The page heading is **Sections**. + +| Setting | Default | What it does | +| --- | --- | --- | +| **Recent folders** | 15 | A slider from 0 to 50. The number of folders in the **Recent** submenu. At 0 the submenu is hidden. | +| **Show open windows** | On | Adds an **Open windows** list with the folders open right now in Explorer and the other supported file managers, up to 10. | +| **Show clipboard folder** | On | Adds a **Clipboard** item when the clipboard holds a folder path, unless it is the folder you are already in. | +| **Show drives** | Off | Adds a **Drives** submenu with the free space on each drive. | +| **Work on the desktop** | On | Also shows the menu on the desktop. Choosing a folder there opens it in a new Explorer window. | + +The folders in **Recent** come from [History](#history). The favorites and the two items at the bottom (**Settings…** and **Reload**) are always there, and the current folder shows whenever Folderite can read it. See [The folder menu](menu.md) for what each section looks like. + +## Dialogs + +Start folders for Open and Save dialogs. [Open and Save dialogs](dialogs.md) explains how the feature behaves, including what wins when both settings apply. + +| Setting | Default | What it does | +| --- | --- | --- | +| **Start in the last folder used** | Off | When an app shows an Open or Save dialog, start it in the folder you last used in that app. | + +Under **Folders for specific apps**, each row sets a fixed start folder for one program: + +| Field | What it does | +| --- | --- | +| App box | The program's file name without `.exe`, for example `notepad` or `winword`. If you type `.exe`, Folderite drops it. Case does not matter. | +| Folder box | The folder the dialog starts in. Environment variables are allowed. | +| Browse button | Opens a folder picker to fill the folder box. | +| Remove this app | Deletes the row. | + +**Add app** adds a row and puts the cursor in its App box. A row with an empty App or Folder box is not saved. A folder set here is used for that app's dialogs even when **Start in the last folder used** is off. + +## Exclusions + +### Don't remember these folders + +Folders that match a pattern here are never added to **Recent** or to [History](#history). Patterns are matched against the whole path, ignoring case. `*` matches any text and `?` matches one character. For example: + +```text +C:\Windows\* +*\node_modules\* +``` + +The **Add pattern** button adds a row and puts the cursor in it. The button at the end of a row removes it. Empty rows are not saved. The patterns also hide folders that are already in history, so a new pattern takes effect for the menu straight away. + +### Don't open the menu in these apps + +Programs listed here are ignored: Folderite does not open its menu over them with the middle button, and it does not set a start folder in their dialogs. The keyboard shortcut still opens the menu with one of them in front, but with no current folder, as for an unsupported window. The list is shown as chips, each with a button to remove it. + +| Control | What it does | +| --- | --- | +| Text box and **Add** | Type a program name without `.exe`, such as `notepad`, and press **Enter** or **Add**. A trailing `.exe` is dropped, and a name already in the list is skipped. | +| **Add app...** | Lists the programs running now that have a window (not Folderite, and not ones already listed). Pick one to add it. | + +## History + +The folders Folderite has seen, ranked by how often and how recently you visit them. The **Recent** submenu uses the same ranking and adds a boost for folders used in the app the menu is opened over. This page reads `history.json`, not `config.json`, and its changes apply right away. The list refreshes each time you open the page. + +Each row shows the folder name, its parent folder, and when you last used it (**just now**, **5 min ago**, **3 h ago**, **yesterday**, **12 days ago**, and a date after 30 days). Hover over a row for the full path. Under the list, a line shows how many folders are listed out of the total. + +| Control | What it does | +| --- | --- | +| **Search folders** | Shows only folders whose path contains the text, ignoring case. | +| Refresh (**Reload the list**) | Reads the list again. | +| Remove | Forgets the selected folder. **Del** does the same. The button is disabled until you select a row. | +| Clear all | Forgets every folder, after asking you to confirm. This can't be undone. | +| Double-click a row | Opens that folder in a new Explorer window. | + +Removing a folder does not stop it coming back: if you visit it again, it is recorded again. To keep a folder out for good, add a pattern under [Exclusions](#dont-remember-these-folders). + +## General + +### Start with Windows + +Off until you turn it on. When on, Folderite starts in the background each time you sign in. It adds a value to your user's `Run` registry key, so it needs no administrator rights. + +### Pause Folderite + +When on, middle-click and the keyboard shortcut do nothing until you turn it off. Left-clicking the tray icon still opens the menu. The state is not saved in `config.json`: it applies to the running Folderite only. If Folderite is not running, the page says **Folderite isn't running.** and shows a **Start Folderite** button instead of the switch. + +### Keyboard shortcut + +A shortcut that opens the folder menu for the window in front, as an alternative to the middle button. Empty by default, which means the middle button only. + +Click the box, then press the keys. Settings records what you press: + +- It needs at least one of Ctrl, Alt, Shift or Win. Otherwise the box says **Add Ctrl, Alt, Shift or Win**. Function keys (F1 to F24) can be used alone. +- **Backspace** or **Delete** clears it, and so does the **Clear** button. **Esc** leaves the box without changing anything. +- Shortcuts are saved as text, such as `Win+Shift+F`, in the `hotkey` key. + +Folderite tries to register the shortcut a moment after you set it. If another program already uses it, a line under the box says **This shortcut is already used by another app. Pick another one.** and a notification may appear from the tray. + +### Configuration + +| Control | What it does | +| --- | --- | +| Config file | The full path of `config.json`. A **Portable** badge appears when the settings sit next to `Folderite.exe`. Under the path, a line says **Default location**, **Custom location** or **Portable**. | +| **Change location…** | Pick a folder to move `config.json` and `history.json` to. See [Files and logs](files.md). | +| **Use default** | Moves them back to `%APPDATA%\Folderite`. Shown only when the location is not the default. | +| **Open file** | Opens `config.json` in your editor. | +| **Open folder** | Opens the settings folder in Explorer. | + +Moving the settings quits Folderite, moves the files and starts it again, so the window reopens afterwards with a note in the status line. If the new folder already has a `config.json`, Settings asks whether to use that one or replace it with your current settings. A replaced file is kept as `config.json.bak`. Choosing the folder that holds `Folderite.exe` asks you to confirm, because it makes Folderite portable. + +### About + +Shows **Folderite** followed by the version you are running. diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 0000000..2876d95 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,422 @@ +/* Folderite look on top of Material, taken from the landing page's styles.css (site/). Light and dark follow the system. */ + +/* ------------------------------------------------------------ tokens */ +:root { + --fl-radius: 12px; + --fl-font: "Segoe UI Variable Text", "Segoe UI Variable", "Segoe UI", system-ui, -apple-system, sans-serif; + --fl-display: "Segoe UI Variable Display", "Segoe UI Variable", "Segoe UI", system-ui, -apple-system, sans-serif; + --fl-mono: "Cascadia Mono", Consolas, "Courier New", monospace; + --fl-ease: cubic-bezier(.2, .8, .2, 1); + --fl-bar: 76px; + /* Material builds --md-text-font-family and --md-code-font-family from these two. */ + --md-text-font: "Segoe UI Variable Text", "Segoe UI Variable", "Segoe UI", system-ui; + --md-code-font: "Cascadia Mono", Consolas, "Courier New"; +} + +/* Light, the landing page's :root. Also on :root so the page scrollbar (drawn by ) has values. */ +:root, +[data-md-color-scheme=default] { + --fl-bg0: #f5f6f8; + --fl-bg1: #ffffff; + --fl-bg2: #eef0f4; + --fl-bg3: #e4e7ec; + --fl-line: rgba(17, 24, 39, .08); + --fl-line2: rgba(17, 24, 39, .15); + --fl-text: #15171c; + --fl-body: #3b414c; + --fl-sub: #646b77; + --fl-yellow: #c98a00; + --fl-yellow-fill: #f2b632; + --fl-amber: #e0670a; + --fl-blue: #0a6cd6; + --fl-blue-2: #3d9bff; + --fl-link: #0a5fc0; + --fl-link-hover: #084c99; + --fl-wash: rgba(10, 108, 214, .07); + --fl-glass: rgba(255, 255, 255, .78); + --fl-shadow: 0 24px 60px rgba(17, 24, 39, .16), 0 2px 6px rgba(17, 24, 39, .06); + --fl-shadow-sm: 0 8px 24px rgba(17, 24, 39, .08); + --fl-orb-y: rgba(242, 182, 50, .42); + --fl-orb-b: rgba(61, 155, 255, .30); + --fl-stage: #d9e2ec; /* backdrop baked into every screenshot */ + color-scheme: light; +} + +/* Dark, the landing page's prefers-color-scheme block. */ +[data-md-color-scheme=slate] { + --fl-bg0: #141416; + --fl-bg1: #1a1b1e; + --fl-bg2: #222327; + --fl-bg3: #2c2d32; + --fl-line: rgba(255, 255, 255, .07); + --fl-line2: rgba(255, 255, 255, .14); + --fl-text: #f0f0f0; + --fl-body: #c8c9ce; + --fl-sub: #92949a; + --fl-yellow: #ffcf4a; + --fl-yellow-fill: #f2b632; + --fl-amber: #ff9f43; + --fl-blue: #2f8cf5; + --fl-blue-2: #5aaeff; + --fl-link: #6cb4ff; + --fl-link-hover: #a3d0ff; + --fl-wash: rgba(61, 155, 255, .09); + --fl-glass: rgba(26, 27, 30, .80); + --fl-shadow: 0 24px 60px rgba(0, 0, 0, .5), 0 0 60px rgba(61, 155, 255, .06); + --fl-shadow-sm: 0 8px 24px rgba(0, 0, 0, .3); + --fl-orb-y: rgba(242, 182, 50, .30); + --fl-orb-b: rgba(61, 155, 255, .28); + --fl-stage: #1f242c; /* dark capture backdrop, see tools/screenshots/capture.ps1 */ + color-scheme: dark; +} + +/* Same dark values on :root, only for the page scrollbar before and outside the body attribute. */ +@media (prefers-color-scheme: dark) { + :root { + --fl-bg0: #141416; + --fl-bg3: #2c2d32; + color-scheme: dark; + } +} + +/* Material's colors, mapped for both schemes. */ +[data-md-color-scheme=default], +[data-md-color-scheme=slate] { + --md-default-bg-color: var(--fl-bg0); + --md-default-fg-color: var(--fl-text); + --md-default-fg-color--light: var(--fl-body); + --md-default-fg-color--lighter: var(--fl-sub); + --md-default-fg-color--lightest: var(--fl-line2); + --md-typeset-color: var(--fl-body); + --md-typeset-a-color: var(--fl-link); + --md-typeset-table-color: var(--fl-line2); + --md-code-bg-color: var(--fl-bg2); + --md-code-fg-color: var(--fl-text); + --md-footer-bg-color: var(--fl-bg1); + --md-footer-bg-color--dark: var(--fl-bg1); + --md-footer-fg-color: var(--fl-text); + --md-footer-fg-color--light: var(--fl-sub); + --md-footer-fg-color--lighter: var(--fl-sub); + --md-admonition-bg-color: var(--fl-bg1); + --md-admonition-fg-color: var(--fl-body); + --md-shadow-z1: var(--fl-shadow-sm); + --md-shadow-z2: var(--fl-shadow-sm); + --md-shadow-z3: var(--fl-shadow); +} + +[data-md-color-primary=custom] { + --md-primary-fg-color: var(--fl-blue); + --md-primary-fg-color--light: var(--fl-blue-2); + --md-primary-fg-color--dark: #0757b0; + --md-primary-bg-color: var(--fl-text); + --md-primary-bg-color--light: var(--fl-sub); +} + +[data-md-color-accent=custom] { + --md-accent-fg-color: var(--fl-link-hover); + --md-accent-fg-color--transparent: var(--fl-wash); +} + +body { + background: var(--fl-bg0); + -webkit-font-smoothing: antialiased; +} + +/* Soft yellow and blue glows behind the top of every page, like the landing page's hero orbs (static). */ +body::before { + content: ""; + position: fixed; inset: 0; z-index: -1; pointer-events: none; + opacity: .8; + background: + radial-gradient(540px circle at max(4%, calc(50% - 560px)) 80px, var(--fl-orb-y), transparent 70%), + radial-gradient(500px circle at min(96%, calc(50% + 620px)) 40%, var(--fl-orb-b), transparent 70%); +} +.md-container, .md-main { background: transparent; } + +:focus-visible { outline: 2px solid var(--fl-blue); outline-offset: 2px; } + +/* ------------------------------------------------------------ top bar */ +.md-header { + height: var(--fl-bar); + padding: 12px 20px 0; + background: transparent; + box-shadow: none !important; + color: var(--fl-text); + pointer-events: none; +} +/* Always the landing page's `.top.scrolled .top-inner` look. */ +.md-header__inner { + max-width: 1180px; + height: 62px; + padding: 0 10px 0 16px; + gap: 16px; + background: var(--fl-glass); + -webkit-backdrop-filter: blur(14px) saturate(1.4); + backdrop-filter: blur(14px) saturate(1.4); + border: 1px solid var(--fl-line2); + border-radius: 16px; + box-shadow: var(--fl-shadow-sm); + pointer-events: auto; +} +.md-header__button { margin: 0; padding: 6px; } +.fl-menu { display: none !important; } +@media screen and (max-width: 76.234375em) { + .fl-menu { display: inline-block !important; } +} + +.fl-brand { + display: flex; align-items: center; gap: 10px; min-width: 0; + color: var(--fl-text) !important; text-decoration: none; + font: 800 19px/1.55 var(--fl-display); letter-spacing: -.02em; +} +.fl-logo { flex: none; width: 30px; height: 30px; } +.fl-docs { + color: var(--fl-body) !important; text-decoration: none; + font-size: 15px; font-weight: 600; + padding: 8px 14px; border-radius: 10px; + transition: color .15s, background .15s; +} +.fl-docs:hover { color: var(--fl-text) !important; background: var(--fl-wash); } +.fl-spacer { flex: 1; } + +.fl-btn { + display: inline-flex; align-items: center; justify-content: center; gap: 8px; + height: 42px; padding: 0 18px; border-radius: 10px; + font: 600 15px var(--fl-font); white-space: nowrap; text-decoration: none; + background: var(--fl-bg1); border: 1px solid var(--fl-line2); + transition: border-color .15s, color .15s, background .15s, transform .15s, box-shadow .15s; +} +.fl-btn svg { width: 18px; height: 18px; fill: currentColor; } +.fl-gh { color: var(--fl-text) !important; } +.fl-gh:hover { color: var(--fl-text) !important; border-color: var(--fl-blue); background: var(--fl-wash); } +.fl-primary { + position: relative; overflow: hidden; isolation: isolate; + color: #fff !important; font-weight: 700; + background: linear-gradient(135deg, var(--fl-blue-2) 0%, var(--fl-blue) 60%, #0757b0 100%); + border-color: transparent; + box-shadow: 0 0 0 1px rgba(10, 108, 214, .35), 0 8px 24px rgba(10, 108, 214, .30); +} +.fl-primary::after { + content: ""; position: absolute; inset: -2px; z-index: -1; pointer-events: none; + background: linear-gradient(105deg, transparent 30%, rgba(255, 255, 255, .45) 50%, transparent 70%); + transform: translateX(-120%); + transition: transform .7s var(--fl-ease); +} +.fl-primary:hover { + color: #fff !important; transform: translateY(-1px); + box-shadow: 0 0 0 1px rgba(10, 108, 214, .55), 0 12px 36px rgba(10, 108, 214, .45); +} +.fl-primary:hover::after { transform: translateX(120%); } +.fl-btn:active { transform: translateY(1px); } +@media (prefers-reduced-motion: reduce) { + .fl-btn, .fl-docs, .fl-primary::after { transition: none; } +} + +@media screen and (max-width: 44.984375em) { + .md-header { padding: 8px 10px 0; } + .md-header__inner { padding: 0 8px; gap: 6px; } + .fl-brand { font-size: 17px; gap: 8px; } + .fl-logo { width: 26px; height: 26px; } + .fl-gh-label, .fl-docs { display: none; } + .fl-gh { width: 42px; padding: 0; } + .fl-primary { padding: 0 14px; } +} + +/* Search: a field in the bar. */ +@media screen and (min-width: 60em) { + .md-search { padding: 0; } + .md-search__form { + border-radius: 10px; + background: var(--fl-bg2); + border: 1px solid var(--fl-line2); + } + .md-search__form:hover { background: var(--fl-bg3); } + .md-search__input { font-size: 14.5px; } + [data-md-toggle=search]:checked ~ .md-header .md-search__form { + border-radius: 10px 10px 0 0; background: var(--fl-bg1); + } + .md-search__output { border-radius: 0 0 10px 10px; } +} +.md-search__input { color: var(--fl-text); } +.md-search__input::placeholder { color: var(--fl-sub); } +.md-search__icon { color: var(--fl-sub); } +.md-search__scrollwrap, .md-search-result { background: var(--fl-bg1); } +.md-search-result__meta { background: var(--fl-bg2); color: var(--fl-sub); } + +/* Content and sidebars start below the floating bar. */ +.md-main__inner { margin-top: calc(var(--fl-bar) - 2.4rem + 1rem); } +@media screen and (min-width: 76.25em) { + .md-nav--primary > .md-nav__title { display: none; } + .md-sidebar { top: calc(var(--fl-bar) + 8px); height: calc(100vh - var(--fl-bar) - 8px); } +} +.md-typeset [id], .md-typeset h1, .md-typeset h2, .md-typeset h3 { scroll-margin-top: calc(var(--fl-bar) + 16px); } + +/* ------------------------------------------------------------ sidebars */ +.md-nav { font-size: 14px; } +.md-nav__title { color: var(--fl-text); font-weight: 800; } +.md-nav--primary .md-nav__item--section > .md-nav__link { + font: 700 11px/1 var(--fl-mono); + letter-spacing: .12em; text-transform: uppercase; + color: var(--fl-sub); + margin-top: 1.4em; +} +.md-nav__link { color: var(--fl-body); transition: color .15s; } +.md-nav__link:hover, .md-nav__link:focus { color: var(--fl-text); } +.md-nav__link--active, .md-nav__item .md-nav__link--active { color: var(--fl-link); font-weight: 600; } +.md-nav--secondary .md-nav__title { color: var(--fl-sub); background: transparent; box-shadow: none; } +@media screen and (max-width: 76.234375em) { + .md-nav--primary .md-nav__title { background: var(--fl-bg1); } + .md-nav--primary .md-nav__item--active > .md-nav__link { color: var(--fl-link); } + .md-sidebar--primary { background: var(--fl-bg0); } + .md-nav__source { background: var(--fl-bg1); } +} + +/* ------------------------------------------------------------ content */ +.md-typeset { font-size: 15.5px; line-height: 1.65; } +.md-typeset h1 { + color: var(--fl-text); + font-family: var(--fl-display); + font-weight: 800; letter-spacing: -.03em; + font-size: 2.4em; line-height: 1.1; margin-bottom: .8em; +} +.md-typeset h2 { + color: var(--fl-text); + font-family: var(--fl-display); + font-weight: 800; letter-spacing: -.02em; + padding-top: .6em; border-top: 1px solid var(--fl-line); +} +.md-typeset h1 + h2, .md-typeset h1 + p + h2 { border-top: 0; } +.md-typeset h3 { color: var(--fl-text); font-family: var(--fl-display); font-weight: 700; letter-spacing: -.01em; } +.md-typeset strong { color: var(--fl-text); } +.md-typeset a { text-underline-offset: 3px; } +.md-typeset a:hover { color: var(--fl-link-hover); } +.md-typeset .headerlink { color: var(--fl-sub); } +.md-typeset .headerlink:hover, .md-typeset [id]:target .headerlink { color: var(--fl-yellow); } + +.md-typeset code { + border-radius: 5px; padding: .1em .35em; + border: 1px solid var(--fl-line); +} +.md-typeset pre > code { border: 0; padding: 1em 1.2em; } +.md-typeset pre { border: 1px solid var(--fl-line); border-radius: 8px; overflow: hidden; } + +.md-typeset kbd { + font: 500 .86em var(--fl-mono); + padding: 1px 6px; border-radius: 5px; + background: var(--fl-bg2); color: var(--fl-text); + border: 1px solid var(--fl-line); border-bottom-width: 2px; box-shadow: none; +} + +.md-typeset table:not([class]) { + font-size: 14px; + background: var(--fl-bg1); + border: 1px solid var(--fl-line2); border-radius: 8px; + box-shadow: none; +} +.md-typeset table:not([class]) th { + background: var(--fl-bg2); color: var(--fl-text); font-weight: 700; +} +.md-typeset table:not([class]) td { border-top: 1px solid var(--fl-line); } +.md-typeset table:not([class]) tr:hover { background: var(--fl-wash); box-shadow: none; } + +/* Screenshots sit on the colour baked into their backdrop, so a rounded corner never shows the page through it. */ +.md-typeset img { + border-radius: var(--fl-radius); + background: var(--fl-stage); + border: 1px solid var(--fl-line2); + box-shadow: var(--fl-shadow); +} + +/* A picture that would show bigger can be clicked (docs/javascripts/lightbox.js). */ +.md-typeset img.fl-zoomable { cursor: zoom-in; transition: border-color .15s var(--fl-ease), box-shadow .15s var(--fl-ease); } +.md-typeset img.fl-zoomable:hover { border-color: var(--fl-blue); } +.md-typeset img.fl-zoomable:focus-visible { outline: 2px solid var(--fl-blue); outline-offset: 3px; } + +/* The lightbox is dark in both themes, so its surfaces and text have their own fixed values. */ +html.fl-lightbox-open { overflow: hidden; } +.fl-lightbox { + --fl-lb-text: #f0f0f0; + --fl-lb-sub: #c8c9ce; + --fl-lb-surface: #222327; + --fl-lb-line: rgba(255, 255, 255, .18); + --fl-lb-accent: #5aaeff; + position: fixed; inset: 0; z-index: 1000; + background: rgba(10, 12, 16, .88); + -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); + opacity: 0; transition: opacity .18s var(--fl-ease); + cursor: zoom-out; +} +.fl-lightbox-pan { position: absolute; inset: 0; display: grid; place-items: center; padding: 24px 20px; } +.fl-lightbox.on { opacity: 1; } +.fl-lightbox figure { margin: 0; display: grid; gap: 12px; justify-items: center; max-width: 100%; max-height: 100%; } +.fl-lightbox img { + display: block; max-width: calc(100vw - 40px); max-height: calc(100vh - 150px); width: auto; height: auto; + border-radius: var(--fl-radius); background: var(--fl-stage); + border: 1px solid var(--fl-lb-line); box-shadow: 0 30px 90px rgba(0, 0, 0, .5); +} +.fl-lightbox figcaption { + width: 0; min-width: min(100%, 720px); text-align: center; /* no wider than the picture, at most 720px */ + color: var(--fl-lb-sub); font-size: 15px; line-height: 1.5; +} +.fl-lightbox-x { + position: fixed; top: 16px; right: 16px; width: 40px; height: 40px; + display: grid; place-items: center; padding: 0; cursor: pointer; + color: var(--fl-lb-text); background: var(--fl-lb-surface); border: 1px solid var(--fl-lb-line); border-radius: 50%; + transition: border-color .15s var(--fl-ease), color .15s var(--fl-ease); +} +.fl-lightbox-x:hover { color: var(--fl-lb-accent); border-color: var(--fl-lb-accent); } +.fl-lightbox-x:focus-visible { outline: 2px solid var(--fl-lb-accent); outline-offset: 3px; } +/* On a phone the picture is already as wide as the screen: show it at its own size and let it be panned. */ +@media (max-width: 700px) { + .fl-lightbox-pan { overflow: auto; place-items: start; padding: 64px 20px 24px; } + .fl-lightbox figure { max-width: none; max-height: none; justify-items: start; } + .fl-lightbox img { max-width: none; max-height: none; } + .fl-lightbox figcaption { position: sticky; left: 20px; width: calc(100vw - 40px); min-width: 0; text-align: left; } +} +@media (prefers-reduced-motion: reduce) { + .fl-lightbox, .md-typeset img.fl-zoomable, .fl-lightbox-x { transition: none; } +} + +.md-typeset .admonition, .md-typeset details { + background: var(--fl-bg1); border-radius: 8px; + border-color: var(--fl-yellow-fill); box-shadow: none; font-size: 14.5px; +} + +.md-content__button { color: var(--fl-sub); } +.md-content__button:hover { color: var(--fl-link); } + +/* ------------------------------------------------------------ footer */ +.md-footer { background: transparent; color: var(--fl-text); } +.md-footer__inner { padding: 8px 0 24px; } +.md-footer__link { + margin: 0 .6rem; padding: 14px 18px; + background: var(--fl-bg1); + border: 1px solid var(--fl-line); border-radius: 10px; + opacity: 1; + transition: border-color .15s, color .15s; +} +.md-footer__link:hover { border-color: var(--fl-blue); color: var(--fl-link); opacity: 1; } +.md-footer__title { font-weight: 700; } +.md-footer__direction { color: var(--fl-sub); font-weight: 400; opacity: 1; } + +/* The landing page's `.foot`. */ +.fl-foot { + border-top: 1px solid var(--fl-line); + background: var(--fl-bg1); + padding: 40px 32px 48px; + text-align: center; color: var(--fl-sub); font-size: 14.5px; + display: grid; gap: 14px; justify-items: center; +} +.fl-foot p { margin: 0; } +.fl-foot-brand { display: flex; align-items: center; gap: 10px; color: var(--fl-text); font-weight: 700; } +.fl-foot-brand img { width: 24px; height: 24px; } +.fl-foot-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 22px; } +.fl-foot-links a { color: var(--fl-sub); text-decoration: none; font-weight: 600; } +.fl-foot-links a:hover { color: var(--fl-blue); } +@media screen and (max-width: 44.984375em) { + .fl-foot { padding-left: 16px; padding-right: 16px; } +} + +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--fl-bg3); border-radius: 10px; border: 2px solid var(--fl-bg0); } diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..b2a47e2 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,173 @@ +# Troubleshooting + +Find what you see below. Messages are quoted as Folderite shows them. Most of them come up as a balloon from the tray icon. Windows can hide balloons (Focus assist, or notifications turned off), so if you expect one and don't see it, check your notification settings. + +## Folderite won't start + +### A dialog says .NET must be installed + +Folderite needs the **.NET 10 Desktop Runtime (x64)**. Without it, Windows shows a message from .NET (not from Folderite) saying that you must install or update .NET to run the application. It usually names the missing framework, `Microsoft.WindowsDesktop.App` version 10, and links to the download. + +Install the [.NET 10 Desktop Runtime for x64](https://dotnet.microsoft.com/download/dotnet/10.0), then start Folderite again. The wording of the dialog depends on your .NET version. See [Install and uninstall](install.md). + +Check that you installed the **Desktop Runtime**, not only the ASP.NET Core Runtime, and the x64 one even if you also have x86. + +### Windows protected your PC (SmartScreen) + +Windows may show a blue "Windows protected your PC" window the first time you run `Folderite.exe`. The release build isn't code signed, and SmartScreen warns about programs it hasn't seen often. Click **More info**, check that the name is `Folderite.exe`, and click **Run anyway**. You only do this once for the file. + +If you'd rather not see it, right-click the downloaded zip, choose **Properties**, tick **Unblock** and click **OK** before you unzip it. + +### I started it and nothing happened + +- **It is already running.** Folderite allows one copy. Starting a second one does nothing, with no message. Look for the folder icon in the notification area, and in the hidden icons behind the **^** arrow. Hover over it: the tooltip reads "Folderite" and its version. +- **It started and quit.** If Folderite can't set up the mouse hook or its message window, it writes the error to `folderite.log` and exits without a window. Open the log (see [What to send in a bug report](#what-to-send-in-a-bug-report)) and send the last lines. +- **The icon vanished after Explorer restarted.** Folderite puts it back by itself when the taskbar is recreated. If it doesn't come back, start Folderite again. + +### It doesn't start with Windows + +Check **Settings > General > Start with Windows**, or the tray menu item **Start with Windows: On**. Windows keeps the exe's path from the moment you switched it on, so if you moved or renamed the Folderite folder, switch it off and on again. See [Registry values](files.md#registry-values). + +## Middle-click does nothing + +Work down this list. The first four are the common ones. + +1. **Folderite is paused.** Right-click the tray icon. If it says **Resume**, click it. The **Pause Folderite** switch in **Settings > General** does the same. A pause also silences the keyboard shortcut. +2. **The app is on the disabled list.** Open **Settings > Exclusions** and look under **Don't open the menu in these apps**, or `disabledProcesses` in [config.json](config.md#disabled-apps). Explorer windows and the desktop are both `explorer`. +3. **Folderite doesn't support that window.** It works in Explorer, standard Open and Save dialogs, Directory Opus, Total Commander, XYplorer, Blender's file browser and the desktop. A program that draws its own file dialog isn't recognized, and a middle-click in a browser tab or an editor keeps its normal meaning on purpose. See [Supported apps](apps.md). +4. **You clicked the tab row.** In an Explorer window with tabs, the title bar row beside the tabs is left alone so that middle-click still closes a tab. Click in the file list instead, or use the [keyboard shortcut](menu.md), which has no such exemption. +5. **The desktop is switched off, or you clicked outside its icon area.** On the desktop, the menu opens from the icon area and needs **Settings > Menu > Work on the desktop** (`enableOnDesktop`) to be on. +6. **The window is elevated.** See [below](#the-window-runs-as-administrator). +7. **Folderite is not running.** Look for its tray icon. **Settings > General** also says "Folderite isn't running." and offers a **Start Folderite** button. + +If the menu opens but the folder doesn't change, see [Picking a folder doesn't work](#picking-a-folder-doesnt-work). + +### The window runs as administrator + +Windows doesn't let a program that isn't elevated watch or control an elevated one. Folderite starts without administrator rights, so in an elevated Explorer window or file dialog the menu may not open, or a picked folder may not be used. To use Folderite there, start it as administrator (right-click `Folderite.exe` and choose **Run as administrator**). A copy started with **Start with Windows** is not elevated. See [Supported apps](apps.md#windows-you-run-as-administrator). + +### The keyboard shortcut does nothing + +The shortcut is set in **Settings > General > Keyboard shortcut**. If it isn't working: + +- **Another program owns it.** When the shortcut can't be registered, a balloon titled "Folderite" appears, and the same text goes to `folderite.log`: + + > *Win+Shift+F* is already used by another app. Pick another shortcut in Settings › General. + + The **General** page shows a warning under the box: "This shortcut is already used by another app. Pick another one." Press a different combination. +- **Folderite can't read the text.** If you typed the value in `config.json` yourself and it isn't a valid shortcut, the balloon says: + + > "*text*" isn't a keyboard shortcut Folderite understands. Set one in Settings › General. + + Settings shows the same warning as for a taken shortcut. See [the format](config.md#keyboard-shortcut). +- **It needs a modifier.** The recording box says "Add Ctrl, Alt, Shift or Win" when you press a plain key. Only function keys (`F1` to `F24`) can be used alone. +- **Folderite is paused.** The shortcut is off while paused. + +Messages for the shortcut appear once, when Folderite starts or when you change the value. + +With a window in front that Folderite doesn't support, the shortcut still opens a menu, without a current folder. Picking a folder then opens a new Explorer window, which is how the tray icon behaves too. + +## The menu is wrong + +### A favorite is missing + +- A folder favorite with no `path`, or a blank one, is skipped. +- A favorite inside a group is in that group's submenu. +- If `config.json` has an error, Folderite uses the last good settings. At startup that means built-in defaults and no favorites. See [Settings says config.json has an error](#settings-says-configjson-has-an-error). +- The **Favorites** page in Settings marks a missing local folder with "This folder doesn't exist." + +### There is no current folder item + +The top item is missing when Folderite can't read the folder: **This PC**, **Home** and search results in Explorer, a window that isn't supported, a dialog whose address bar can't be read, and Blender. It is also absent when you open the menu from the tray icon. See [Supported apps](apps.md). + +### My actions are missing + +Actions are in the submenu that opens from the current folder item, so you need a current folder. From the tray icon they don't appear. Check the **Actions** page too: with no `actions` key in the file you get the one default action, `Open &terminal here`. + +### An action doesn't run + +A balloon titled "Folderite" says: + +> *Open in VS Code* failed: *reason* + +The reason is Windows' own text, for example that the file can't be found. Check `command` on the **Actions** page: it must be a program on your `PATH` or a full path, and `%NAME%` variables are expanded. See [Actions](config.md#actions). + +### The Recent submenu is empty or short + +- **Menu > Recent folders** may be `0` (`recentCount`). +- The folder you are in is left out of its own list. +- Folders matching a pattern in **Exclusions** are never recorded, and hidden if they were recorded earlier. See [History exclusions](config.md#history-exclusions). +- Folderite records folders from windows it supports only, and not in an app on the disabled list. +- You cleared the history on the **History** page. + +### The Drives submenu is missing + +It is off by default. Turn on **Settings > Menu > Show drives**. + +## Picking a folder doesn't work + +- **A balloon says "Couldn't open *folder* in *Explorer*."** The last word names the window type: Explorer, File dialog, Directory Opus, Total Commander, XYplorer, Blender or Desktop. Folderite asked the window to go there and the window refused or couldn't be reached. The window may have closed or be elevated, or the folder may not exist. Try again, and check [Supported apps](apps.md) for that program. +- **A new Explorer window opens instead.** That is the design when the menu was opened from the tray icon, from the keyboard shortcut over an unsupported window, or on the desktop. +- **A dialog doesn't start in my folder.** Check [Open and Save dialogs](dialogs.md). A start folder is used only if the folder exists. When a dialog never took the folder, Folderite writes "Couldn't move the *program* file dialog to *folder*." to `folderite.log`. + +## A folder or drive is not available + +A network share that is asleep, a mapped drive that is disconnected or a USB drive that has been removed can't be read. Folderite is built so the menu does not freeze: + +- **A favorite with subfolders** reads the folder when you open its submenu and waits at most 1.5 seconds. If the folder doesn't answer, the submenu shows the folder itself and no subfolders. Open it again when the share is back. +- **The Drives submenu** reads all drives together under the same 1.5 second limit. A drive that doesn't answer is listed without its free space. +- **A plain favorite** is not checked when the menu is built. Picking one that is gone fails in the window you were in, or shows the "Couldn't open ... in ..." balloon above. +- **Settings** marks a favorite on a missing drive letter with "This folder doesn't exist." Network (`\\server\share`) paths are not checked. A disconnected mapped drive counts as missing. +- **Recent** keeps showing a folder after it has gone. Remove it on the **History** page, or leave it, as folders you don't use fade down the list. +- **A dialog start folder** that doesn't exist is ignored, and the dialog opens where it normally would. + +## Settings says config.json has an error + +You see a red banner at the top of the Settings window: + +> config.json has an error, so changes here are not saved. + +Below it is the message from the JSON reader, which names the line and position, with the buttons **Open file** and **Reload**. The status line at the bottom reads "Autosave is off: config.json has errors", and a change you make shows "Not saved: fix config.json or reload". At startup, and when you choose **Reload** in the tray menu, Folderite shows the same message in a balloon titled "Folderite: config error". + +If you break the file while Settings is open, the banner appears the next time you change something there. + +Settings stops saving on purpose. If it kept going, it would overwrite the file you are in the middle of fixing with its own copy of the last good settings. In the meantime Folderite keeps using those last good settings. + +To fix it: + +1. Click **Open file** (or **Edit config…** in the tray menu), and go to the line in the message. +2. Look for the usual causes: a missing comma between two entries, a missing closing `}` or `]`, a single backslash in a path (it must be `\\`), quotes around a number or around `true` or `false`, or an unquoted key. Comments (`//`) and trailing commas are fine. +3. Save the file. Folderite and Settings pick it up on their own after a moment ("Reloaded: config.json changed on disk"). **Reload** in Settings reads it again at once. + +If you can't find the mistake, rename `config.json` to something else and restart Folderite. It creates a fresh one with four favorites, and you can copy the parts you want from the old file. The full rules are in [When the file has a mistake](config.md#when-the-file-has-a-mistake). + +A different message, "Not saved:" followed by a reason, means the file couldn't be written: the folder is read-only, the disk is full, or another program holds the file. Move the files ([Files and logs](files.md#moving-the-files)) or fix the permissions. + +## Moving the settings failed + +**Change location…** and **Use default** show one of these in a dialog titled Folderite: + +- "Folderite didn't close, so the settings were not moved." Folderite has to stop while the files move. It didn't quit within a few seconds. Quit it from the tray icon and try again. +- A Windows error text, such as access being denied or a path not found. Your files are still in the old place; Settings reopens there with the note "Settings were not moved". + +See [Files and logs](files.md#moving-the-files) for what happens when the new folder already holds settings. + +## Settings won't open + +Choosing **Settings…** starts a second copy of `Folderite.exe` with `--settings`. If Windows refuses, a balloon says "Could not open settings:" with the reason. If the window never appears, look in `folderite.log` for an `ERROR` line from that moment. If Settings is already open, the command brings it to the front instead. + +## A problem with one app + +Each program has its own limits and its own way to tell it is working: see [Supported apps](apps.md). The usual ones are Explorer's tab row, a Directory Opus panel that isn't in front, and Blender, where the current folder can't be read. + +## What to send in a bug report + +Report problems at [github.com/combwizard/Folderite/issues](https://github.com/combwizard/Folderite/issues). It helps to include: + +- **The version.** Hover over the tray icon: the tooltip reads "Folderite 0.3.0", or similar. **Settings > General** shows the same at the bottom. +- **Your Windows version**, and the program you were in (and its version). +- **What you did and what you saw**, step by step, and what you expected. Say whether you used the mouse or the keyboard shortcut, and quote any balloon text. +- **`folderite.log`**, or its last lines. It sits next to `config.json`. Click **Open config folder** in the tray menu, or **Open folder** under **Settings > General**. The file exists only after something was logged, and Folderite deletes it and starts a new one when it passes about 1 MB. See [The log](files.md#the-log). +- **`config.json`**, if it may matter. Remove any folder names you don't want to share. + +Config errors are not written to the log, so for those send the message from the balloon or the Settings banner. If Folderite stopped on its own, the log's last `ERROR` entry is the most useful thing you can send. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..4aacb7a --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,83 @@ +# Folderite documentation, published at https://combwizard.github.io/Folderite/docs/ by .github/workflows/pages.yml. +# Preview locally: pip install -r requirements-docs.txt then mkdocs serve +site_name: Folderite +site_description: A middle-click folder menu for Windows. +site_url: https://combwizard.github.io/Folderite/docs/ +repo_url: https://github.com/combwizard/Folderite +repo_name: combwizard/Folderite +edit_uri: edit/main/docs/ +copyright: Folderite is free software under the MIT license. + +docs_dir: docs +# Never the default. MkDocs defaults to site/ and empties it first, and site/ is the landing page's source here. +site_dir: site/.preview/docs + +nav: + - Home: index.md + - Getting started: + - install.md + - getting-started.md + - Using Folderite: + - menu.md + - settings.md + - dialogs.md + - apps.md + - config.md + - files.md + - troubleshooting.md + - Under the hood: + - how-it-works.md + - building.md + +theme: + name: material + # Header, footer and logo are reshaped to match the landing page. + custom_dir: docs-theme + favicon: assets/icon.png + logo: assets/icon.png + font: false + icon: + repo: fontawesome/brands/github + # Follows the system's light or dark mode, like the landing page: docs-theme/partials/javascripts/palette.html. + palette: + scheme: default + primary: custom + accent: custom + features: + - navigation.sections + - navigation.footer + - navigation.top + - navigation.instant + - search.suggest + - search.highlight + - toc.follow + - content.code.copy + - content.action.edit + +plugins: + - search + +extra_css: + - stylesheets/extra.css + +# Click a picture to see it larger. +extra_javascript: + - javascripts/lightbox.js + +extra: + homepage: https://combwizard.github.io/Folderite/ + generator: false + +markdown_extensions: + - admonition + - attr_list + - md_in_html + - tables + - toc: + permalink: true + - pymdownx.superfences + - pymdownx.details + +validation: + links: + anchors: warn diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..78df485 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1 @@ +mkdocs-material>=9.7,<10 diff --git a/site/index.html b/site/index.html index 0577ba7..6584898 100644 --- a/site/index.html +++ b/site/index.html @@ -11,12 +11,12 @@ - - + + - + @@ -35,9 +35,10 @@ Apps Install FAQ + Docs - GitHub - Download + GitHub + Download @@ -56,8 +57,8 @@

Folderite

A middle-click folder menu for Windows

Middle-click in Explorer or any Open/Save dialog and a menu of your folders opens at the cursor: favorites, folders open in other windows, recent ones and the one on the clipboard. Pick one and that window goes there.

  • Windows 10 1903+ / 11
  • @@ -262,7 +263,7 @@

    Get the .NET 10 Desktop Runtime

  • Download and unzip

    -

    Grab Folderite-<version>-win-x64.zip from the latest release and unzip it to any folder you like.

    +

    Grab Folderite-<version>-win-x64.zip from the latest release and unzip it to any folder you like.

  • Run Folderite.exe

    @@ -318,7 +319,7 @@

    Common questions

    Windows says it “protected my PC”. Is that expected?
    -

    It can be. The exe isn't code-signed, so SmartScreen may warn about it the first time it runs. Click More info, then Run anyway. The release zip is built from the public source by GitHub Actions, and you can always build it yourself.

    +

    It can be. The exe isn't code-signed, so SmartScreen may warn about it the first time it runs. Click More info, then Run anyway. The release zip is built from the public source by GitHub Actions, and you can always build it yourself.

    @@ -342,7 +343,7 @@

    Common questions

    Is it free?
    -

    Yes. Folderite is open source under the MIT license. Bug reports and ideas are welcome on GitHub Issues.

    +

    Yes. Folderite is open source under the MIT license. Bug reports and ideas are welcome on GitHub Issues.

    @@ -353,8 +354,8 @@

    Common questions

    Give Folderite a try

    Free and open source, for Windows 10 and 11.

    @@ -363,10 +364,11 @@

    Give Folderite a try

    Folderite

    A middle-click folder menu for Windows. MIT license. Made by combwizard.

    diff --git a/site/preview.ps1 b/site/preview.ps1 index d8cb91d..256363d 100644 --- a/site/preview.ps1 +++ b/site/preview.ps1 @@ -1,4 +1,4 @@ -# Stage the site and serve it locally. Ctrl+C stops the server. +# Stage the site, build the docs into it and serve it locally. Ctrl+C stops the server. # Usage: powershell -ExecutionPolicy Bypass -File preview.ps1 [-Port 8000] param( [int]$Port = 8000 @@ -6,6 +6,7 @@ param( $ErrorActionPreference = 'Stop' $Site = $PSScriptRoot +$Repo = Split-Path $Site -Parent $Preview = Join-Path $Site '.preview' # Refuse if the port is already taken. @@ -16,5 +17,10 @@ if ($taken) { & (Join-Path $Site 'stage.ps1') -Out $Preview +# The docs go under /docs/, as on the published site. Needs: pip install -r requirements-docs.txt +$env:NO_MKDOCS_2_WARNING = '1' +python -m mkdocs build --strict --quiet -f (Join-Path $Repo 'mkdocs.yml') -d (Join-Path $Preview 'docs') +if ($LASTEXITCODE -ne 0) { throw 'mkdocs build failed.' } + Write-Host "Serving http://127.0.0.1:$Port/ (Ctrl+C to stop)" python -m http.server $Port --bind 127.0.0.1 --directory $Preview diff --git a/site/stage.ps1 b/site/stage.ps1 index 1e1911b..144f916 100644 --- a/site/stage.ps1 +++ b/site/stage.ps1 @@ -20,10 +20,10 @@ foreach ($f in $pages) { Copy-Item $src (Join-Path $OutFull $f) } -# Screenshots and animations from docs/ (top level only). +# Screenshots and animations from docs/images/. $imagesOut = Join-Path $OutFull 'images' New-Item -ItemType Directory -Path $imagesOut | Out-Null -Get-ChildItem -LiteralPath (Join-Path $Repo 'docs') -File | +Get-ChildItem -LiteralPath (Join-Path $Repo 'docs/images') -File | Where-Object { $_.Extension -in '.png', '.gif' } | ForEach-Object { Copy-Item $_.FullName (Join-Path $imagesOut $_.Name) } diff --git a/tools/screenshots/capture.ps1 b/tools/screenshots/capture.ps1 index 3b164a6..9268383 100644 --- a/tools/screenshots/capture.ps1 +++ b/tools/screenshots/capture.ps1 @@ -1,5 +1,5 @@ # Captures the README/website screenshots and animations in the CURRENT Windows theme and writes -# docs\-light.* or docs\-dark.*. Every capture is needed in both themes: switch Windows +# docs\images\-light.* or docs\images\-dark.*. Every capture is needed in both themes: switch Windows # between light and dark mode (Settings > Personalization > Colors) and run this once in each. # # Takes over the mouse and keyboard for about a minute; don't touch them until it says "done". @@ -13,7 +13,7 @@ $ErrorActionPreference = 'Stop' $here = $PSScriptRoot $repo = (Resolve-Path (Join-Path $here '..\..')).Path $work = Join-Path $here '.work' -$docs = Join-Path $repo 'docs' +$docs = Join-Path $repo 'docs\images' $light = (Get-ItemPropertyValue 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize' 'AppsUseLightTheme') -eq 1 $theme = if ($light) { 'light' } else { 'dark' } From 58f300579a991220c784ef21829fa3a6efd49c98 Mon Sep 17 00:00:00 2001 From: combwizard <18099578+combwizard@users.noreply.github.com> Date: Wed, 4 Nov 2026 11:54:12 +0100 Subject: [PATCH 2/2] Repo renamed to Folderite: security policy and release lookup URLs --- .github/SECURITY.md | 4 ++-- site/site.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 511b933..8e69578 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -2,13 +2,13 @@ ## Supported versions -Only the [latest release](https://github.com/combwizard/folderite/releases/latest) gets security fixes. If you're on an older version, upgrade first and check whether the problem is still there. +Only the [latest release](https://github.com/combwizard/Folderite/releases/latest) gets security fixes. If you're on an older version, upgrade first and check whether the problem is still there. ## Reporting a vulnerability Please don't open a public issue for security problems. Report them privately through GitHub instead: -1. Go to the [Security tab](https://github.com/combwizard/folderite/security) of this repository. +1. Go to the [Security tab](https://github.com/combwizard/Folderite/security) of this repository. 2. Click **Report a vulnerability**. Include the Folderite version, your Windows version, the steps to reproduce, and what an attacker could do with it. diff --git a/site/site.js b/site/site.js index 08991e8..f068599 100644 --- a/site/site.js +++ b/site/site.js @@ -109,7 +109,7 @@ // ---- Latest release version ---- - const RELEASE_API = 'https://api.github.com/repos/combwizard/folderite/releases/latest'; + const RELEASE_API = 'https://api.github.com/repos/combwizard/Folderite/releases/latest'; const CACHE_KEY = 'folderite-release'; const CACHE_MS = 10 * 60 * 1000;